/***************************************************************************
 *   mgpi_roeskva.c - Roeskva Graphic Engine Definition Source File        *
 *   Copyright (C) 2008 by cmaster.matso (Maciej Kadłubowski)              *
 *   cmaster.matso@gmail.com                                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "mgpi_engine.h"

enum { FCT, CTA, CTB, DT };														/** postprocessing texture names indices */

/* TODO: put it into a structure... */
static float s_pFogColor[] = {0.02f, 0.02f, 0.02f, 1.0f};
static float s_pFogBoundries[] = {1000.0f, 10000.0f};
static float s_nFogDensity = 0.025f;

static int s_setProperty(pname_t property, ...);

/**
 * Sets up GL state machine for texturing.
 */
static void s_textured(rendnode_t *ptr) {
    if (!glIsEnabled(ptr->tex_targ)) {
    	glEnable(ptr->tex_targ);
        if (s_fTexturingFlag) glBindTexture(ptr->tex_targ, ptr->tex_id);
    }
    return;
}

/**
 * Sets up GL state machine for 2x multiplicative blending.
 */
static void s_mult2xBlending(rendnode_t *ptr) {/* TEST IT!!! */
    if (!glIsEnabled(GL_BLEND)) glEnable(GL_BLEND);
    glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
    return;
}

/**
 * Sets up GL state machine for multiplicative blending.
 */
static void s_multBlending(rendnode_t *ptr) {/* TEST IT!!! */
    if (!glIsEnabled(GL_BLEND)) glEnable(GL_BLEND);
    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
    return;
}

/**
 * Sets up GL state machine for additive blending.
 */
static void s_additiveBlending(rendnode_t *ptr) {/* TEST IT!!! */
    if (!glIsEnabled(GL_BLEND)) glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    return;
}

/**
 * Sets up GL state machine for alpha blending.
 */
static void s_alphaBlending(rendnode_t *ptr) {/* TEST IT!!! */
    if (!glIsEnabled(GL_BLEND)) glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    return;
}

/**
 * Sets up a framebuffer for "render-to-texture".
 */
static void s_framebufferR2T(rendnode_t *ptr) {
	if (ptr->fbuffer.multisample > 0) {
		glBindFramebuffer(GL_FRAMEBUFFER, ptr->fbuffer.multisample);
	} else {
		glBindFramebuffer(GL_FRAMEBUFFER, ptr->fbuffer.used_id);
	}
    return;
}

/**
 * Sets up the object for 2D rendering.
 */
static void s_render2D(rendnode_t *ptr) {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    return;
}

/**
 * Causes the selected program to be used for rendering.
 */
static void s_shaders(rendnode_t *ptr) {
    if (glIsProgram(ptr->shaders.program_id))
        glUseProgram(ptr->shaders.program_id);
#if defined GL_ARB_vertex_program && GL_ARB_fragment_program
    else if (s_fUsePrecompiledShaders) {
        glEnable(GL_VERTEX_PROGRAM_ARB);
        glEnable(GL_FRAGMENT_PROGRAM_ARB);
        if (glIsProgramARB(ptr->shaders.shaders_programs[0]))
            glBindProgramARB(GL_VERTEX_PROGRAM_ARB, ptr->shaders.shaders_programs[0]);

        /*if (glIsProgramNV(ptr->shaders.shaders_programs[1]))
            glBindProgramARB(GL_GEOMETRY_PROGRAM_NV, ptr->shaders.shaders_programs[1]);*/

        if (glIsProgramARB(ptr->shaders.shaders_programs[2]))
            glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, ptr->shaders.shaders_programs[2]);
    }
#endif
    return;
}

/**
 * Causes the GL to be setup for multitexture rendering.
 */
static void s_multitextured(rendnode_t *ptr) {
    int n = 0, m = 0, i;
    struct _multitex *names = ptr->mt_ptr;
    m = ptr->flag;

    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);

    for (i = 0; i < n && i < m; ++i) {
        glActiveTexture(GL_TEXTURE0 + i);
        if (!glIsEnabled(names[i].tex_targ)) glEnable(names[i].tex_targ);
        glBindTexture(names[i].tex_targ, names[i].tex_name);

        if (s_fUseShaders)
            if (glIsProgram(ptr->shaders.program_id)) {
                glUniform1i(names[i].location, i);
            }
    }
    return;
}

/**
 * Sets up a linear fog effect.
 */
static void s_fogLinear(rendnode_t *ptr) {
    float *fogColor = s_pFogColor;
    float fogStart = s_pFogBoundries[X];
    float fogEnd = s_pFogBoundries[Y];

    if (!glIsEnabled(GL_FOG)) glEnable(GL_FOG);

    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_START, fogStart);
    glFogf(GL_FOG_END, fogEnd);
    glFogfv(GL_FOG_COLOR, fogColor);

    return;
}

/**
 * Sets up an exponential fog effect.
 */
static void s_fogExponent(rendnode_t *ptr) {
    float *fogColor = s_pFogColor;
    float fogDensity = s_nFogDensity;

    if (!glIsEnabled(GL_FOG)) glEnable(GL_FOG);

    glFogi(GL_FOG_MODE, GL_EXP);
    glFogf(GL_FOG_DENSITY, fogDensity);
    glFogfv(GL_FOG_COLOR, fogColor);

    return;
}

/**
 * Sets up a quadric exponential fog effect.
 */
static void s_fogExponent2(rendnode_t *ptr) {
    float *fogColor = s_pFogColor;
    float fogDensity = s_nFogDensity;

    if (!glIsEnabled(GL_FOG)) glEnable(GL_FOG);

    glFogi(GL_FOG_MODE, GL_EXP2);
    glFogf(GL_FOG_DENSITY, fogDensity);
    glFogfv(GL_FOG_COLOR, fogColor);

    return;
}

/**
 * Passes user-defined uniform variables to active program.
 */
static void s_variables(rendnode_t *ptr) {
    int i, type, location, length;
    float *fptr;

    for (i = 0; i < ptr->gl_var->n; ++i) {
        type = ptr->gl_var[i].type;
        location = ptr->gl_var[i].location;
        fptr = ptr->gl_var[i].pointer;
        length = ptr->gl_var[i].length;

        switch (type) {
            case MGPI_GLSL_FLOAT1:
                glUniform1f(location, *fptr);
                break;

            case MGPI_GLSL_FLOAT2:
                glUniform2f(location, fptr[0], fptr[1]);
                break;

            case MGPI_GLSL_FLOAT3:
                glUniform3f(location, fptr[0], fptr[1], fptr[2]);
                break;

            case MGPI_GLSL_FLOAT4:
                glUniform4f(location, fptr[0], fptr[1], fptr[2], fptr[3]);
                break;

            case MGPI_GLSL_FLOATnv:
                glUniform4fv(location, length, fptr);
                break;

            case MGPI_GLSL_INT1:
                glUniform1i(location, (int) (*fptr));
                break;

            case MGPI_GLSL_INT2:
                glUniform2i(location, (int) fptr[0], (int) fptr[1]);
                break;

            case MGPI_GLSL_INT3:
                glUniform3i(location, (int) fptr[0], (int) fptr[1], (int) fptr[2]);
                break;

            case MGPI_GLSL_INT4:
                glUniform4i(location, (int) fptr[0], (int) fptr[1], (int) fptr[2], (int) fptr[3]);
                break;

            case MGPI_GLSL_INTnv:
                glUniform4iv(location, length, (int *) fptr);
                break;

            case MGPI_GLSL_MATRIX2:
                glUniformMatrix2fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX3:
                glUniformMatrix3fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX4:
                glUniformMatrix4fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX2x3:
                glUniformMatrix2x3fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX3x2:
                glUniformMatrix3x2fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX2x4:
                glUniformMatrix2x4fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX4x2:
                glUniformMatrix4x2fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX3x4:
                glUniformMatrix3x4fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX4x3:
                glUniformMatrix4x3fv(location, length, 0, fptr);
                break;

            case MGPI_GLSL_MATRIX2_T:
                glUniformMatrix2fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX3_T:
                glUniformMatrix3fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX4_T:
                glUniformMatrix4fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX2x3_T:
                glUniformMatrix2x3fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX3x2_T:
                glUniformMatrix3x2fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX2x4_T:
                glUniformMatrix2x4fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX4x2_T:
                glUniformMatrix4x2fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX3x4_T:
                glUniformMatrix3x4fv(location, length, 1, fptr);
                break;

            case MGPI_GLSL_MATRIX4x3_T:
                glUniformMatrix4x3fv(location, length, 1, fptr);
                break;

            default: break;
        }
    }
    return;
}

/* TODO: and more gl_commands... */

/**
 * Disables blending.
 */
static void s_disableBlending(rendnode_t *ptr) {/* TEST IT!!! */
    if (glIsEnabled(GL_BLEND)) glDisable(GL_BLEND);
    return;
}

/**
 * Disables texturing.
 */
static void s_disableTextured(rendnode_t *ptr) {
    if (glIsEnabled(ptr->tex_targ)) {
    	glDisable(ptr->tex_targ);
        if (s_fTexturingFlag) glBindTexture(ptr->tex_targ, 0);
    }
    return;
}

/**
 * Disables "render-to-texture".
 */
static void s_disableFramebufferR2T(rendnode_t *ptr) {
	glGenerateMipmap(ptr->fbuffer.target);
	if (ptr->fbuffer.multisample > 0) {
		glBindFramebuffer(GL_READ_FRAMEBUFFER, ptr->fbuffer.multisample);
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, ptr->fbuffer.used_id);
		glBlitFramebuffer(0, 0, ptr->fbuffer.w, ptr->fbuffer.h, 0, 0, ptr->fbuffer.w, ptr->fbuffer.h, 
			GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	} else {
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
    return;
}


/**
 * Disables 2D rendering.
 */
static void s_disableRender2D(rendnode_t *ptr) {/* TEST IT!!! */
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}

/**
 * Disables multitexturing.
 */
static void s_disableMultitextured(rendnode_t *ptr) {
    int n = 0, m = 0, i;
    struct _multitex *names = ptr->mt_ptr;
    m = ptr->flag;

    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);

    for (i = 0; i < n && i < m; ++i) {
        glActiveTexture(GL_TEXTURE0 + i);
        glDisable(names[i].tex_targ);
		glBindTexture(names[i].tex_targ, 0);
    }

    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
    return;
}

/**
 * Disable shaders.
 */
static void s_disableShaders(rendnode_t *ptr) {
    /*glUseProgram(0);*/

    if (s_fUsePrecompiledShaders) {
#if defined GL_ARB_vertex_program && GL_ARB_fragment_program
        glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
        /*glBindProgramNV(GL_GEOMETRY_PROGRAM_NV, 0);*/
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
        glDisable(GL_VERTEX_PROGRAM_ARB);
        glDisable(GL_FRAGMENT_PROGRAM_ARB);
#endif
    }

    return;
}

/**
 * Disables any fog effect.
 */
static void s_disableFog(rendnode_t *ptr) {
    if (glIsEnabled(GL_FOG)) glDisable(GL_FOG);
    return;
}

/**
 * Renders a given MD5 model.
 */
static int s_renderModelMD5(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;
    md5_model_t *mp = (md5_model_t *) obj->rd_ptr;
    md5_anim_t *anim = (md5_anim_t *) obj->an_ptr;
    int start = s_nAnimStart;	/* Note: this way of doing animation is highly inefficient */
    int stop = s_nAnimStop;

    glFrontFace(GL_CW);
    glCullFace(GL_BACK);

    if (obj->gl_commands_pre && !s_fBlockCommands) {
        obj->gl_commands_pre(obj);
        used = MGPI_TRUE;
    }

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);

        gsubRenderMD5Model(mp, anim, start, stop, s_nNumPasses); /* Note: texturing issue - no texture coordinates! */
    }
    glPopMatrix();

    if (obj->gl_commands_post && !s_fBlockCommands && used)
        obj->gl_commands_post(obj);

	glFrontFace(GL_CCW);

    return (!0);
}

/**
 * Renders a given OBJ model.
 */
static int s_renderModelOBJ(rendnode_t *o) {
    MGPI_BOOL used = MGPI_FALSE;
    obj_model_t *obj = (obj_model_t *) o->rd_ptr;
	int n, i;
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &n);

    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);

    glPushMatrix();
    {
    	if (o->gl_transformations) o->gl_transformations(o);
		if (o->gl_commands_pre && !s_fBlockCommands) {
			o->gl_commands_pre(o);
			used = MGPI_TRUE;
		}
		if (o->gl_vert_attribs->number > 0 && o->gl_vert_attribs->number < n)
			for (i = 0; i < o->gl_vert_attribs->number; ++i) {
				glEnableVertexAttribArray(o->gl_vert_attribs[i].index);
				glVertexAttribPointer(o->gl_vert_attribs[i].index,
									  o->gl_vert_attribs[i].size,
									  o->gl_vert_attribs[i].type,
									  o->gl_vert_attribs[i].normalized,
									  o->gl_vert_attribs[i].stride,
									  o->gl_vert_attribs[i].pointer);
			}

        gsubRenderOBJModel(obj);

		if (o->gl_vert_attribs->number > 0 && o->gl_vert_attribs->number < n)
			for (i = 0; i < o->gl_vert_attribs->number; ++i) glDisableVertexAttribArray(o->gl_vert_attribs[i].index);
		if (o->gl_commands_post && !s_fBlockCommands && used) {
			o->gl_commands_post(o);
		}
    }
    glPopMatrix();
    return (!0);
}

/**
 * Renders SMD model.
 */
//static int s_renderModelSMD(rendnode_t *obj) {/* OUT!!! */
//    MGPI_BOOL used = MGPI_FALSE;
//    smd_model_t *smd = (smd_model_t *) obj->rd_ptr;
//
//    glFrontFace(GL_CCW);
//    glCullFace(GL_BACK);
//
//    if (obj->gl_commands_pre && !s_fBlockCommands) {
//        obj->gl_commands_pre(obj);
//        used = MGPI_TRUE;
//    }
//
//    glPushMatrix();
//    {
//        if (obj->gl_transformations) obj->gl_transformations(obj);
//
//        gsubAnimateSMD(smd);
//        gsubRenderSMDModel(smd);
//    }
//    glPopMatrix();
//
//    if (obj->gl_commands_post && !s_fBlockCommands && used)
//        obj->gl_commands_post(obj);
//
//    return (!0);
//}

/**
 * Renders a whole array of sprite particles.
 */
static int s_renderSpriteParticles(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glEnable(GL_POINT_SPRITE);
    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);
		if (obj->gl_commands_pre && !s_fBlockCommands) {
	        obj->gl_commands_pre(obj);
		    used = MGPI_TRUE;
	    }

        /* TODO: implementation */

		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();

    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_FALSE);
    glDisable(GL_POINT_SPRITE);

    return (!0);
}

/**
 * Renders a whole array of bilboard particles. ????
 */
static int s_renderBilboardParticles(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glPushMatrix();
    {
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

        /* TODO: implementation */
		
		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();

    return (!0);
}

/**
 * Renders a whole array of point particles.
 */
static int s_renderPointParticles(rendnode_t *obj) {/* TEST IT!!! */
    MGPI_BOOL used = MGPI_FALSE;

    clrpart_t *particles = (clrpart_t *) obj->rd_ptr;

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_COLOR_ARRAY);

        glVertexPointer(4, GL_FLOAT, 0, particles->vertex_array);
        glNormalPointer(GL_FLOAT, 0, particles->normal_array);
        glColorPointer(4, GL_FLOAT, 0, particles->color_array);

        /*glDrawArrays(GL_POINTS, 0, particles->num_parts);*/
        glDrawElements(GL_POINTS, particles->num_indices, GL_UNSIGNED_INT, particles->index_array);

        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
		
		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();


    return (!0);
}

/**
 * Renders a single bilboard.
 */
static int s_renderBildoard(rendnode_t *obj) {

    return (!0);
}

/**
 * Renders a single sprite.
 */
static int s_renderSprite(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glDisable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

        glBegin(GL_QUADS);
        {
            glTexCoord2f(0.0f, 1.0f);
            glVertex3f(-1.0f, -1.0f, 0.0f);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f(-1.0f, 1.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f);
            glVertex3f(1.0f, 1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f);
            glVertex3f(1.0f, -1.0f, 0.0f);
        }
        glEnd();
		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();


    glEnable(GL_CULL_FACE);
    glDisable(GL_BLEND);

    return (!0);
}

/**
 * Renders a height map.
 */
static int s_renderHeightmap(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glFrontFace(GL_CW);
    glCullFace(GL_BACK);

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

        gsubRenderHMPModel((hmp_t *) obj->rd_ptr);
		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();


    return (!0);
}

/**
 * Renders a BSP map.
 */
static int s_renderBSPMap(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

        /* TODO: read about BSP format... */

		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
    }
    glPopMatrix();

    return (!0);
}

/**
 * Renders a sky box (using cube mapping).
 */
static int s_renderSkyboxCubemap(rendnode_t *obj) {
    GLfloat fExtent = 1.0f;

    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);

        glEnable(GL_TEXTURE_CUBE_MAP);
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_GEN_R);
        glBindTexture(GL_TEXTURE_CUBE_MAP, obj->tex_id);
        glBegin(GL_QUADS);
        {
            glTexCoord3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(-fExtent, -fExtent, fExtent);

            glTexCoord3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(-fExtent, -fExtent, -fExtent);

            glTexCoord3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(-fExtent, fExtent, -fExtent);

            glTexCoord3f(-1.0f, 1.0f, 1.0f);
            glVertex3f(-fExtent, fExtent, fExtent);

            glTexCoord3f(1.0f, -1.0f, -1.0f);
            glVertex3f(fExtent, -fExtent, -fExtent);

            glTexCoord3f(1.0f, -1.0f, 1.0f);
            glVertex3f(fExtent, -fExtent, fExtent);

            glTexCoord3f(1.0f, 1.0f, 1.0f);
            glVertex3f(fExtent, fExtent, fExtent);

            glTexCoord3f(1.0f, 1.0f, -1.0f);
            glVertex3f(fExtent, fExtent, -fExtent);

            glTexCoord3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(-fExtent, -fExtent, -fExtent);

            glTexCoord3f(1.0f, -1.0f, -1.0f);
            glVertex3f(fExtent, -fExtent, -fExtent);

            glTexCoord3f(1.0f, 1.0f, -1.0f);
            glVertex3f(fExtent, fExtent, -fExtent);

            glTexCoord3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(-fExtent, fExtent, -fExtent);

            glTexCoord3f(1.0f, -1.0f, 1.0f);
            glVertex3f(fExtent, -fExtent, fExtent);

            glTexCoord3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(-fExtent, -fExtent, fExtent);

            glTexCoord3f(-1.0f, 1.0f, 1.0f);
            glVertex3f(-fExtent, fExtent, fExtent);

            glTexCoord3f(1.0f, 1.0f, 1.0f);
            glVertex3f(fExtent, fExtent, fExtent);

            glTexCoord3f(-1.0f, 1.0f, 1.0f);
            glVertex3f(-fExtent, fExtent, fExtent);

            glTexCoord3f(-1.0f, 1.0f, -1.0f);
            glVertex3f(-fExtent, fExtent, -fExtent);

            glTexCoord3f(1.0f, 1.0f, -1.0f);
            glVertex3f(fExtent, fExtent, -fExtent);

            glTexCoord3f(1.0f, 1.0f, 1.0f);
            glVertex3f(fExtent, fExtent, fExtent);

            glTexCoord3f(-1.0f, -1.0f, -1.0f);
            glVertex3f(-fExtent, -fExtent, -fExtent);

            glTexCoord3f(-1.0f, -1.0f, 1.0f);
            glVertex3f(-fExtent, -fExtent, fExtent);

            glTexCoord3f(1.0f, -1.0f, 1.0f);
            glVertex3f(fExtent, -fExtent, fExtent);

            glTexCoord3f(1.0f, -1.0f, -1.0f);
            glVertex3f(fExtent, -fExtent, -fExtent);
        }
        glEnd();
        glDisable(GL_TEXTURE_CUBE_MAP);
    }
    glPopMatrix();

    return (!0);
}

/**
 * Renders a sky box.
 */
static int s_renderSkybox(rendnode_t *obj) {
    MGPI_BOOL used = MGPI_FALSE;

    glFrontFace(GL_CW);
    glCullFace(GL_BACK);

    if (obj->gl_commands_pre && !s_fBlockCommands) {
        obj->gl_commands_pre(obj);
        used = MGPI_TRUE;
    }

    glPushMatrix();
    {
        if (obj->gl_transformations) obj->gl_transformations(obj);

        glBindTexture(GL_TEXTURE_2D, obj->tex_id);
        glBegin(GL_QUADS);
        {
            glTexCoord2f(0.25f, (float) (1.0f / 3.0f));
            glVertex3f(-1.0f, 1.0f, -1.0f);
            glTexCoord2f(0.5f, (float) (1.0f / 3.0f));
            glVertex3f(1.0f, 1.0f, -1.0f);
            glTexCoord2f(0.5f, (float) (2.0f / 3.0f));
            glVertex3f(1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.25f, (float) (2.0f / 3.0f));
            glVertex3f(-1.0f, -1.0f, -1.0f);

            glTexCoord2f(1.0f, (float) (1.0f / 3.0f));
            glVertex3f(-1.0f, 1.0f, 1.0f);
            glTexCoord2f(1.0f, (float) (2.0f / 3.0f));
            glVertex3f(-1.0f, -1.0f, 1.0f);
            glTexCoord2f(0.75f, (float) (2.0f / 3.0f));
            glVertex3f(1.0f, -1.0f, 1.0f);
            glTexCoord2f(0.75f, (float) (1.0f / 3.0f));
            glVertex3f(1.0f, 1.0f, 1.0f);

            glTexCoord2f(0.5f, (float) (1.0f / 3.0f));
            glVertex3f(1.0f, 1.0f, -1.0f);
            glTexCoord2f(0.75f, (float) (1.0f / 3.0f));
            glVertex3f(1.0f, 1.0f, 1.0f);
            glTexCoord2f(0.75f, (float) (2.0f / 3.0f));
            glVertex3f(1.0f, -1.0f, 1.0f);
            glTexCoord2f(0.5f, (float) (2.0f / 3.0f));
            glVertex3f(1.0f, -1.0f, -1.0f);

            glTexCoord2f(0.0f, (float) (1.0f / 3.0f));
            glVertex3f(-1.0f, 1.0f, 1.0f);
            glTexCoord2f(0.25f, (float) (1.0f / 3.0f));
            glVertex3f(-1.0f, 1.0f, -1.0f);
            glTexCoord2f(0.25f, (float) (2.0f / 3.0f));
            glVertex3f(-1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.0f, (float) (2.0f / 3.0f));
            glVertex3f(-1.0f, -1.0f, 1.0f);

            glTexCoord2f(0.25f, 0.0f);
            glVertex3f(-1.0f, 1.0f, 1.0f);
            glTexCoord2f(0.5f, 0.0f);
            glVertex3f(1.0f, 1.0f, 1.0f);
            glTexCoord2f(0.5f, (float) (1.0f / 3.0f));
            glVertex3f(1.0f, 1.0f, -1.0f);
            glTexCoord2f(0.25f, (float) (1.0f / 3.0f));
            glVertex3f(-1.0f, 1.0f, -1.0f);

            glTexCoord2f(0.25f, (float) (2.0f / 3.0f));
            glVertex3f(-1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.5f, (float) (2.0f / 3.0f));
            glVertex3f(1.0f, -1.0f, -1.0f);
            glTexCoord2f(0.5f, 1.0f);
            glVertex3f(1.0f, -1.0f, 1.0f);
            glTexCoord2f(0.25f, 1.0f);
            glVertex3f(-1.0f, -1.0f, 1.0f);
        }
        glEnd();
    }
    glPopMatrix();

    if (obj->gl_commands_post && !s_fBlockCommands && used)
        obj->gl_commands_post(obj);

    return (!0);
}

/**
 * Renders a quad to the screen.
 */
static int s_renderScreenAlignedQuad(rendnode_t *obj) {
	MGPI_BOOL used = MGPI_FALSE;

	glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
	{
		if (obj->gl_commands_pre && !s_fBlockCommands) {
			obj->gl_commands_pre(obj);
			used = MGPI_TRUE;
		}

		glBegin(GL_QUADS);
		{
			glTexCoord2f(1, 0);
			glVertex2f(1, -1);

			glTexCoord2f(1, 1);
			glVertex2f(1, 1);

			glTexCoord2f(0, 1);
			glVertex2f(-1, 1);

			glTexCoord2f(0, 0);
			glVertex2f(-1, -1);
		}
		glEnd();

		if (obj->gl_commands_post && !s_fBlockCommands && used)
			obj->gl_commands_post(obj);
	}
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

	return (!0);
}

/**
 * Renders a quad to the screen.
 */
static int s_renderScreenAlignedQuadMultipass(rendnode_t *obj) {
	MGPI_BOOL flag = MGPI_TRUE;
	int current = 0, i;
	struct _multitex *names = obj->mt_ptr;

	if (s_fDoMultipassProcess) {
		// Copy FCT to CTA for the first pass (they contain the same data)
		g3dCopyTexture2D(GL_FLOAT, GL_RGBA, s_nScreenWidth, s_nScreenHeight, s_nPPTextures[FCT], s_nPPTextures[CTA]);
		s_setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, MGPI_DEFAULT_FRAMEBUFFER,
							  s_nPPTextures[CTB], s_nScreenWidth, s_nScreenHeight);

		if (!names) {
			MGPI_PostMessagef("%s: Multipass postprocessing textures object not initialized!", __FUN__);
			ENGINE_Exit();
			exit(EXIT_FAILURE);
		}

		// CTA, FCT and DT goes as the first pass input
		for (i = 0; i < MGPI_MULTIPASS_PROCESS_TEXTURES; ++i)	names[i].tex_targ = GL_TEXTURE_2D;
		names[0].tex_name = s_nPPTextures[FCT];
		names[1].tex_name = s_nPPTextures[CTA];
		names[2].tex_name = s_nPPTextures[DT];

		while (current < s_pPPEffect.num_passes &&
			   glIsProgram(s_pPPEffect.passes[current]) &&
			   current < MGPI_MAX_PASSES_PER_EFFECT)
		{														// loop only when the current pass exists
			if ((current + 1) < s_pPPEffect.num_passes)
			{
				// Render quad to texture
				s_setProperty(MGPI_RENDER_TARGET, MGPI_TEXTURE, MGPI_ENABLE);
				{
					obj->shaders.program_id = s_pPPEffect.passes[current];
					s_renderScreenAlignedQuad(obj);
				}
				s_setProperty(MGPI_RENDER_TARGET, MGPI_TEXTURE, MGPI_DISABLE);

				// Do the texture swapping
				if (!flag) {
					// CTB is input, CTA is output
					names[1].tex_name = s_nPPTextures[CTB];
					s_setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, MGPI_DEFAULT_FRAMEBUFFER,
							  s_nPPTextures[CTA], s_nScreenWidth, s_nScreenHeight);
				} else {
					// CTA is input, CTB is output
					names[1].tex_name = s_nPPTextures[CTA];
					s_setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, MGPI_DEFAULT_FRAMEBUFFER,
							  s_nPPTextures[CTB], s_nScreenWidth, s_nScreenHeight);
				}
				flag = !flag;
			} else {
				// Draw the last effect pass to the screen
				obj->shaders.program_id = s_pPPEffect.passes[current];
				s_renderScreenAlignedQuad(obj);
			}
			++current;		// go to next pass
		}
	}
	return (!0);
}

/**
 * Enqueues a rendering object to rendering queue.
 */
static int s_loadRenderingObject(void *rptr, int type) {
    if (s_nListFlag) {
        static int element_id = 0;
        rendnode_t *iteration = s_pRenderingList;
        rendnode_t *temp = (rendnode_t *) calloc(1, sizeof (rendnode_t));

        if (!iteration) {
            s_pRenderingList = temp;
            s_pRenderingList->type = type;
            s_pRenderingList->rd_ptr = rptr;
            s_pRenderingList->mt_ptr = NULL;
            s_pRenderingList->an_ptr = NULL;
            s_pRenderingList->next = s_pRenderingList->prev = NULL;
            s_pRenderingList->elem_id = element_id;
            s_pRenderingList->shaders.shaders = NULL;
            s_pRenderingList->shaders.num = 0;
            s_pRenderingList->shaders.program_id = 0;
            s_pRenderingList->gl_commands_pre = NULL;
            s_pRenderingList->gl_commands_post = NULL;
            s_pRenderingList->tex_id = -1;
            s_pRenderingList->tex_targ = GL_TEXTURE_2D;
            s_pRenderingList->gl_transformations = NULL;
            s_pRenderingList->number_of_cmds = 0;
            s_pRenderingList->combined_cmds_array = NULL;
            s_pRenderingList->flag = 0;
            s_pRenderingList->usrcmds.free = 0;
            s_pRenderingList->usrcmds.num = 0;
            s_pRenderingList->usrcmds.current = 0;
            s_pRenderingList->gl_var->n = 0;
			s_pRenderingList->gl_vert_attribs->number = 0;

            ++element_id;

            return s_pRenderingList->elem_id;
        }

        while (iteration->next) iteration = iteration->next;
        temp->prev = iteration;
        iteration->next = temp;
        temp->next = NULL;
        temp->type = type;
        temp->rd_ptr = rptr;
        temp->mt_ptr = NULL;
        temp->an_ptr = NULL;
        temp->elem_id = element_id;
        temp->shaders.shaders = NULL;
        temp->shaders.num = 0;
        temp->shaders.program_id = 0;
        temp->combined_cmds_array = NULL;
        temp->number_of_cmds = 0;
        temp->flag = 0;
        temp->usrcmds.free = 0;
        temp->usrcmds.num = 0;
        temp->usrcmds.current = 0;
        temp->tex_targ = GL_TEXTURE_2D;
        temp->gl_var->n = 0;
		temp->gl_vert_attribs->number = 0;

        ++element_id;

        return temp->elem_id;
    } else {
        if (s_nFree < MGPI_MAX_RENDERING_OBJECTS) {
            s_pRenderingArray[s_nFree].next = NULL;
            s_pRenderingArray[s_nFree].prev = NULL;
            s_pRenderingArray[s_nFree].elem_id = s_nFree;
            s_pRenderingArray[s_nFree].flag = 0;
            s_pRenderingArray[s_nFree].gl_commands_post = NULL;
            s_pRenderingArray[s_nFree].gl_commands_pre = NULL;
            s_pRenderingArray[s_nFree].gl_transformations = NULL;
            s_pRenderingArray[s_nFree].number_of_cmds = 0;
            s_pRenderingArray[s_nFree].mt_ptr = NULL;
            s_pRenderingArray[s_nFree].an_ptr = NULL;
            s_pRenderingArray[s_nFree].rd_ptr = rptr;
            s_pRenderingArray[s_nFree].tex_id = 0;
            s_pRenderingArray[s_nFree].tex_targ = GL_TEXTURE_2D;
            s_pRenderingArray[s_nFree].type = type;
            s_pRenderingArray[s_nFree].shaders.shaders = NULL;
            s_pRenderingArray[s_nFree].shaders.num = 0;
            s_pRenderingArray[s_nFree].shaders.program_id = 0;
            s_pRenderingArray[s_nFree].usrcmds.free = 0;
            s_pRenderingArray[s_nFree].usrcmds.num = 0;
            s_pRenderingArray[s_nFree].usrcmds.current = 0;
            s_pRenderingArray[s_nFree].gl_var->n = 0;
			s_pRenderingArray[s_nFree].gl_vert_attribs->number = 0;

            ++s_nFree;
        } else {
            MGPI_PostMessagef("%s: max number of rendering objects reached", __FUN__);
            ENGINE_Exit();
            exit(EXIT_FAILURE);
        }
        return (s_nFree - 1);
    }
}

/**
 * Dequeues a rendering object form the rendering queue.
 */
static int s_callRenderingObject(void) {
    rendnode_t *temp;

    if (s_nListFlag) {
        temp = s_pRenderingList;

        if (temp) {/* ????? */
            s_pRenderingList = temp->next;
            s_pAuxPointer = temp;
            return (!0);
        }

        s_pRenderingList = temp->next;
        s_pRenderingList->prev = NULL;
        temp->next = NULL;

        switch (temp->type) {
            case MGPI_MODEL_MD5:
                s_renderModelMD5(temp);
                break;

            case MGPI_MODEL_OBJ:
                ((obj_model_t *) temp->rd_ptr)->tex_id = temp->tex_id;
                s_renderModelOBJ(temp);
                break;

			case MGPI_SCREEN_QUAD_MULTIPASS:
				s_renderScreenAlignedQuadMultipass(temp);
				break;

			case MGPI_SCREEN_QUAD:
				s_renderScreenAlignedQuad(temp);
                break;

            case MGPI_POINT_PARTICLE_ARRAY:
                s_renderPointParticles(temp);
                break;

            case MGPI_SPRITE_PARTICLE_ARRAY:
                s_renderSpriteParticles(temp);
                break;

            case MGPI_BILBOARD_PARTICLE_ARRAY:
                s_renderBilboardParticles(temp);
                break;

            case MGPI_SPRITE:
                s_renderSprite(temp);
                break;

            case MGPI_BILBOARD:
                s_renderBildoard(temp);
                break;

            case MGPI_HEIGHTMAP:
                s_renderHeightmap(temp);
                break;

            case MGPI_BSPMAP:
                /*s_renderBSPMap(temp);*/
                break;

            case MGPI_SKYBOX:
                s_renderSkybox(temp);
                break;

            case MGPI_SKYBOX_CUBEMAP:
                s_renderSkyboxCubemap(temp);
                break;

            case MGPI_USER_DEFINED:
                glPushMatrix();
            {
                MGPI_BOOL used = MGPI_FALSE;

                if (temp->gl_transformations) temp->gl_transformations(temp);
                if (temp->gl_commands_pre && s_fBlockCommands) {
                    temp->gl_commands_pre(temp);
                    used = MGPI_TRUE;
                }

                s_pRenderUserDefined(temp);

                if (temp->gl_commands_post && s_fBlockCommands && used)
                    temp->gl_commands_post(temp);
            }
                glPopMatrix();
                break;

            default: return 0;
        }

        free(temp);
    }

    return (!0);
}

/**
 * Generates an GL rendering list.
 */
static int s_genGLList(void) {
    if (s_nListFlag) {
        int list = glGenLists(1);

        glNewList(list, GL_COMPILE);
        {
            while (s_callRenderingObject());
        }
        glEndList();

        return list;
    }
    return -1;
}

/**
 * Renders a given GL rendering list.
 */
static int s_renderGLList(int list) {
    glClear(s_fClearFlags);
    g3dUpdateCam(g_nCurrentCameraHandle);

    glCallList(list);

    SDL_GL_SwapBuffers();
    return list;
}

/**
 * Deletes a given GL rendering list.
 */
static int s_deleteGLList(int list) {
    if (glIsList((GLuint) list)) glDeleteLists(list, 1);
    return list;
}

/**
 * Renders a given string in the given position.
 */
static void s_renderString(float3 pos, float3 color, void *font, char *string) {
    char *c;
    glColor3f(color[0], color[1], color[2]);
    glRasterPos3f(pos[0], pos[1], pos[2]);

    for (c = string; *c != '\0'; c++) glutBitmapCharacter(font, *c);

    glRasterPos3f(0, 0, 0);
    glColor3f(1.0f, 1.0f, 1.0f);
    return;
}

/**
 * Gets an object form the engine rendering list.
 */
static rendnode_t *s_getObject(int id) {
    if (s_nListFlag) {
        rendnode_t *iteration = s_pRenderingList;
        while (iteration) {
            if (iteration->elem_id == id) return iteration;
            iteration = iteration->next;
        }
    } else {
        if (id >= 0 && (id < s_nFree && id < MGPI_MAX_RENDERING_OBJECTS))
            return &s_pRenderingArray[id];
    }
    return NULL;
}

/**
 * Select rendering object for processing.
 */
static int s_selectObject(int hobj) {
    static int i = 0;

    s_pAuxPointer = s_getObject(hobj);
    if (!s_pAuxPointer) {
        MGPI_PostMessagef("%s: object of the given ID does not exist", __FUN__);
        return -1;
    }

    if (s_nSelectedSection != MGPI_DEFAULT) {
        if (i < MGPI_MAX_RENDERING_OBJECTS) {
            s_vSections[s_nSelectedSection][i] = hobj;
            ++i;
        }
    }

    return hobj;
}

/**
 * Creates a set of num of rendering sections.
 */
static int s_genSections(int num, int *sections) {/* TEST IT!!! */
    int i, k = 0;
    static int j = 0;

    if (j < MGPI_MAX_RENDERING_SECTIONS && num < (MGPI_MAX_RENDERING_SECTIONS - j)) {
        for (i = 0; i < num; ++i) {
            if (!s_vSections[i]) {
                s_vSections[i] = (int *) calloc(MGPI_MAX_RENDERING_OBJECTS, sizeof (int));
                memset(s_vSections[i], -1, MGPI_MAX_RENDERING_OBJECTS); /* ??? */
                MGPI_CollectGarbage(s_vSections[i]);
                sections[k] = i;
                k++;
            }
        }
        j += num;
    }

    return num;
}

/**
 * Selects a section of the given ID for processing.
 */
static int s_selectSection(int id) {/* TEST IT!!! */
    s_nSelectedSection = id;
    return id;
}

/**
 * Loads a given type of a rendering object form the file.
 */
static int s_loadObject(const char *path, rendobj_t type) {
    int i = 0/*, tex = 0*/;
    /*char **sbx = (char **) calloc(6, sizeof (char *));*/
    /*char *name = NULL;*/
    /*void *ptr;*/

    SDL_Surface *surface = NULL;
    /*mdl_model_t *mdl = NULL;
    md2_model_t *md2 = NULL;*/
    md5_model_t *md5 = NULL;
    md5_anim_t *anim = NULL;
    obj_model_t *obj = NULL;
    hmp_t *hmp = NULL;

	//const struct aiScene *ais;

    switch (type) {

        case MGPI_MODEL_MD5:
            if (strstr(path, ".md5mesh")) {

                md5 = (md5_model_t *) calloc(1, sizeof (md5_model_t));
                MGPI_CollectGarbage(md5);
                anim = (md5_anim_t *) calloc(1, sizeof (md5_anim_t));
                MGPI_CollectGarbage(anim);

                if (!gsubReadMD5Model(path, md5, anim)) return -1;
                i = s_loadRenderingObject(md5, MGPI_MODEL_MD5);
                s_getObject(i)->an_ptr = anim;
                return i;

            } else {
                MGPI_PostMessagef("%s: invalid file extention given: can be .md5", __FUN__);
                return -1;
            }

        case MGPI_MODEL_SMD: return 0; /* TODO: */

        case MGPI_MODEL_OBJ:
            if (strstr(path, ".obj")) {
                obj = (obj_model_t *) calloc(1, sizeof (obj_model_t));
                MGPI_CollectGarbage(obj);
                if (!gsubReadOBJModel(path, obj)) return -1;
                return s_loadRenderingObject(obj, MGPI_MODEL_OBJ);
            } else {
                MGPI_PostMessagef("%s: invalid file extention given: can be .obj", __FUN__);
                return -1;
            }

        case MGPI_BILBOARD: return 0; /* TODO: */

        case MGPI_SKYBOX_CUBEMAP:
            i = s_loadRenderingObject(NULL, MGPI_SKYBOX_CUBEMAP);
            return i;

        case MGPI_SKYBOX:
            i = s_loadRenderingObject(NULL, MGPI_SKYBOX);
            return i;

		case MGPI_SCREEN_QUAD_MULTIPASS:
		case MGPI_SCREEN_QUAD:
			i = s_loadRenderingObject(NULL, type);
			return i;

        case MGPI_SPRITE:
            if (strstr(path, ".bmp") || strstr(path, ".png") || strstr(path, ".jpg")) {
                if (!(surface = IMG_Load(path))) {
                    MGPI_PostMessagef("%s: no such file or directory: %s", __FUN__, path);
                    return -1;
                }
                i = s_loadRenderingObject(NULL, MGPI_SPRITE);
                s_getObject(i)->tex_id = g3dGenTexture2DRGBA(surface->pixels, surface->w, surface->h);
                SDL_FreeSurface(surface);
                return i;
            } else {
                MGPI_PostMessagef("%s: invalid file extention given: can be .bmp, .png or .jpg", __FUN__);
                return -1;
            }

        case MGPI_HEIGHTMAP:
            if (strstr(path, ".bmp") || strstr(path, ".png") || strstr(path, ".jpg")) {
                hmp = (hmp_t *) calloc(1, sizeof (hmp_t));
                MGPI_CollectGarbage(hmp);
                if (!gsubReadHMPModel(path, hmp)) return -1;
                return s_loadRenderingObject(hmp, MGPI_HEIGHTMAP);
            } else {
                MGPI_PostMessagef("%s: invalid file extention given: can be .bmp, .png or .jpg", __FUN__);
                return -1;
            }

        case MGPI_BSPMAP: return 0; /* TODO: */
        case MGPI_USER_DEFINED:
            return s_loadRenderingObject(NULL, MGPI_USER_DEFINED);

        default: break;
    }

    MGPI_PostMessagef("%s: invalid file given: can be .obj, .png, .bmp, .bsp only.", __FUN__);
    return -1;
}

/**
 * TODO:
 */
static int s_releaseObject(int hobj) {

	if (s_nListFlag) {
		rendnode_t *obj = s_getObject(hobj);
		rendnode_t *l, *r;

		if (obj) {
			l = obj->prev;
			r = obj->next;

			l->next = r;
			r->prev = l;

			/* release the node */

		}
	} else {
		/* ... */
	}

    return (!0);
}

/**
 * Loads an object for multipass rendering.
 */
static int s_loadComposition(const char *path, rendobj_t type, int npass) {/* TEST IT!!! */
    int id = s_loadObject(path, type);
    rendnode_t *obj = s_getObject(id);
    int i;

    for (i = id + 1; i < npass; ++i) s_loadRenderingObject(obj->rd_ptr, type);

    return id;
}

/**
 * Releases rendering node compositions.
 */
static int s_releaseComposition(int hobj, int npass) {

	int i;
    for (i = hobj; i < npass; ++i) s_releaseObject(i);

    return (!0);
}

/**
 * Causes a given object to be rendered.
 */
static int s_renderObject(int hobj) {
    rendnode_t *obj = s_getObject(hobj);

    if (!obj) {
        MGPI_PostMessagef("%s: given handle is of no valid object: %d", __FUN__, hobj);
        return 0;
    }

    switch (obj->type) {

        case MGPI_MODEL_MD5:
            s_renderModelMD5(obj);
            break;

        case MGPI_MODEL_OBJ:
            ((obj_model_t *) obj->rd_ptr)->tex_id = obj->tex_id;
            s_renderModelOBJ(obj);
            break;

        case MGPI_POINT_PARTICLE_ARRAY:
            s_renderPointParticles(obj);
            break;

        case MGPI_SPRITE_PARTICLE_ARRAY:
            s_renderSpriteParticles(obj);
            break;

        case MGPI_BILBOARD_PARTICLE_ARRAY:
            s_renderBilboardParticles(obj);
            break;

        case MGPI_SPRITE:
            s_renderSprite(obj);
            break;

        case MGPI_BILBOARD:
            s_renderBildoard(obj);
            break;

        case MGPI_HEIGHTMAP:
            s_renderHeightmap(obj);
            break;

        case MGPI_BSPMAP:
            /*s_renderBSPMap(obj);*/
            break;

		case MGPI_SCREEN_QUAD_MULTIPASS:
			s_renderScreenAlignedQuadMultipass(obj);
			break;

		case MGPI_SCREEN_QUAD:
			s_renderScreenAlignedQuad(obj);
            break;

        case MGPI_SKYBOX:
            s_renderSkybox(obj);
            break;

        case MGPI_SKYBOX_CUBEMAP:
            s_renderSkyboxCubemap(obj);
            break;

        case MGPI_USER_DEFINED:
            glPushMatrix();
			{
				MGPI_BOOL used = MGPI_FALSE;

				if (obj->gl_transformations) obj->gl_transformations(obj);
				if (obj->gl_commands_pre && !s_fBlockCommands) {
					obj->gl_commands_pre(obj);
					used = MGPI_TRUE;
				}

				s_pRenderUserDefined(obj);

				if (obj->gl_commands_post && !s_fBlockCommands && used)
					obj->gl_commands_post(obj);
			}
            glPopMatrix();
            break;
    }
    return (!0);
}

/**
 * Causes the object of the given ID to be rendered in given number of passes.
 */
static int s_renderComposition(int hobj, int npass) {/* TEST IT!!! */
    int i;

    s_nNumPasses = npass;
    for (i = hobj; i < npass; ++i) s_renderObject(i);
    s_nNumPasses = 1;

    return 0;
}

/**
 * Causes an object of the given ID to be rendered. Here it is done asynchronous.
 */
static int s_renderObjectAsync(void *ptr) {
    int hobj = *((int *) ptr);

    free(ptr);

    return s_renderObject(hobj);
}

/**
 * Causes the object of the given ID to be rendered in given number of passes.
 * Here it is done asynchronous.
 */
static int s_renderCompositionAsync(void *ptr) {
    int hobj = ((int *) ptr)[0];
    int npass = ((int *) ptr)[1];

    free(ptr);

    return s_renderComposition(hobj, npass);
}

/**
 * Causes a given object to be rendered. Here it is done asynchronous.
 */
static void s_callRenderObjectAsync(int hobj) {/* TEST IT!!! */
    int *param = (int *) calloc(1, sizeof (int));
    param[0] = hobj;

    SDL_CreateThread(s_renderObjectAsync, param);
    return;
}

/**
 * Causes the object of the given ID to be rendered in given number of passes.
 * Here it is done asynchronous.
 */
static void s_callRenderCompositionAsync(int hobj, int npass) {/* TEST IT!!! */
    int *param = (int *) calloc(2, sizeof (int));

    param[0] = hobj;
    param[1] = npass;
    SDL_CreateThread(s_renderCompositionAsync, param);
    return;
}

/**
 * Causes a rendering section of the given ID to be rendered.
 */
static void s_renderSection(int id) {/* deprecated */
    int *section = s_vSections[id], i = 0;
    rendnode_t *obj = s_getObject(section[0]);

    if (obj) {
        if (obj->gl_commands_pre) {
            obj->gl_commands_pre(obj);
        }

        while (section[i] >= 0) {
            if (!i) s_fBlockCommands = MGPI_TRUE;
            else s_fBlockCommands = MGPI_FALSE;
            s_renderObject(section[i]);
            ++i;
        }

        if (obj->gl_commands_post) {
            obj->gl_commands_post(obj);
        }
    }

    return;
}

/**
 * Sets the given trasformations routine as a transformations of the object selected.
 */
static int s_setTransformations(void (*trans)(rendnode_t *)) {
    rendnode_t *obj = s_pAuxPointer;

    if (obj) obj->gl_transformations = trans;
    return 0;
}

/**
 * Sets the given trasformations routine as a transformations of the object given.
 */
static int s_setObjectTransformations(int hobj, void (*trans)(rendnode_t *)) {
    rendnode_t *obj = s_getObject(hobj);

    if (!obj) {
        MGPI_PostMessagef("%s: given handle is of no valid object: %d", __FUN__, hobj);
        return -1;
    }

    obj->gl_transformations = trans;
    return hobj;
}

/**
 * Loads an animation of a given name for the given object.
 */
static int s_loadAnimation(int hobj, const char *path) {/* TEST IT!!! */
    rendnode_t *obj = s_getObject(hobj);

    if (!obj) {
        MGPI_PostMessagef("%s: given handle is of no valid object: %d", __FUN__, hobj);
        return 0;
    }

    switch (obj->type) {
        case MGPI_MODEL_MD5:
            gsubReadMD5Anim(path, (md5_anim_t *) obj->an_ptr, (md5_model_t *) obj->rd_ptr);
            break;

            /* i inne... */

        default: break;
    }

    return (!0);
}

/**
 * Verifies the setup of the GLSL.
 */
static int s_verifyGLSL(const char *name, GLuint obj, int type) {
    int infologLength = 0, linkStatus = 0;
    int charsWritten = 0;
    char *infoLog;

    switch (type) {
        case MGPI_GLSL_PROGRAM:
            glGetProgramiv(obj, GL_LINK_STATUS, &linkStatus);
            if (linkStatus == GL_FALSE) {
                MGPI_PostMessagef("%s: program linking failed", __FUN__);
                return 0;
            }
            break;

        case MGPI_GLSL_SHADER:
            glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
            if (infologLength > 1) {
                infoLog = (char *) calloc(infologLength, sizeof (char));
                glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
                MGPI_PostMessagef("%s: shader: \'%s\' \n%s", __FUN__, name, infoLog);

				if (strstr(infoLog, "warning")) {
					free(infoLog);
					return (!0);
				}

                free(infoLog);
                return 0;
            }

        default: break;
    }
    return (!0);
}

/**
 * Causes the given code string to be compiled to a shader object.
 */
static GLuint s_setupShader(const char *name, pname_t attrib, int n, const char **code, const int *lengths) {
    GLuint h = 0;

    if (s_fUseShaders) {
		h = glCreateShader(attrib);
        glShaderSource(h, n, code, lengths);
        glCompileShader(h);
        s_verifyGLSL(name, h, MGPI_GLSL_SHADER);
    }
    return h;
}

/**
 * Causes the given shaders array to be linked to the shader program.
 */
static GLuint s_setupProgram(GLuint *shaders, int length) {
    GLuint p = 0;
    int i;

    if (s_fUseShaders) {
        if (s_nSelectedProgramID == s_nMainProgramID) p = s_nMainProgramID;
        else p = glCreateProgram();
        for (i = 0; i < length; ++i)
            if (glIsShader(shaders[i])) glAttachShader(p, shaders[i]);
            else {
                MGPI_PostMessagef("%s: given shaders array contains some invalid shaders", __FUN__);
                return 0;
            }
        glLinkProgram(p);
        s_verifyGLSL("", p, MGPI_GLSL_PROGRAM);
    }

    return p;
}

/**
 * Prepares postporcessing effect.
 */
static int s_setupEffect(const char *passes[], int n, effect_t *e) {
	int i, j, m;
	const char *code[MGPI_MAX_PASSES_PER_EFFECT], *texnames[MGPI_MULTIPASS_PROCESS_TEXTURES] 
														= { "frame", "last", "depth" };
	GLuint vf[2];

	if (e) {
		if (n-1 <= MGPI_MAX_PASSES_PER_EFFECT) {
			e->num_passes = n-1;
			for (i = 0; i < n; ++i) {
				code[i] = glswGetShader(passes[i]); // first of them need to be a vertex shader!!!!

				if (!code[i]) {
					MGPI_PostMessagef("%s: Pass code (%s) does not exist!", __FUN__, passes[i]);
					return MGPI_FALSE;
				}
			}

			// Build programs for the effect passes
			vf[0] = s_setupShader(passes[0], MGPI_VERTEX_SHADER, 1, code, NULL);
			m = s_nSelectedProgramID;
			s_nSelectedProgramID = -1;

			for (i = 0; i < e->num_passes + 1; ++i) {
				vf[1] = s_setupShader(passes[i], MGPI_FRAGMENT_SHADER, 1, code + i + 1, NULL);
				e->passes[i] = s_setupProgram(vf, 2);

				// Find locations of the input textures in the pass
				for (j = 0; j < MGPI_MULTIPASS_PROCESS_TEXTURES; ++j)
					e->locations[i][j] = glGetUniformLocation(e->passes[i], texnames[j]);
			}

			s_nSelectedProgramID = m;
		}
		return (!0);
	}
	return 0;
}

/**
 * Selects what program is to be used for variable setup.
 */
static void s_selectProgram(int node) {
    if (node == MGPI_DEFAULT) s_nSelectedProgramID = s_nMainProgramID;
    else if (node <= MGPI_OBJECT) s_nSelectedProgramID = s_pAuxPointer->shaders.program_id;
    else s_nSelectedProgramID = (GLuint) node;
    return;
}

/**
 * Creates a set of framebuffers.
 */
static void s_genFramebuffers(int num) {
    int n = 0, i;

    s_nFramebuffersNumber = num;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);
    s_nMaxAttachments += n;
    for (i = 0; i < MGPI_MAX_FRAMEBUFFERS; ++i)
        s_aAttachments[i] = (GLuint *) calloc(n + 2, sizeof (GLuint));

	s_oFramebufferNode.fbuffer.depth = 0;
	s_oFramebufferNode.fbuffer.stencil = 0;
	s_oFramebufferNode.fbuffer.multisample = 0;

    glGenFramebuffers(num, s_aFramebuffers);
    return;
}

/**
 * Deletes a set of framebuffers.
 */
static void s_deleteFramebuffers(void) {/* !!!!!!!!! */
    //int n = 0, i;

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDeleteFramebuffers(s_nFramebuffersNumber, s_aFramebuffers);

    //glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);
    //for (i = 0; i < MGPI_MAX_FRAMEBUFFERS; ++i) 
	//	free(s_aAttachments[i]);

    s_nFramebuffersNumber = 0;
    return;
}

/**
 * Calls a whole set of engine predefined GL commands.
 */
static void s_combinedCommands(rendnode_t *ptr) {
    int i = 0;

    while (i < ptr->number_of_cmds) {
        if (!s_fBlockCommands)
            switch (ptr->combined_cmds_array[i]) {
                case MGPI_GL_CMD_TEXTURE:
                    s_textured(ptr);
                    break;

                case MGPI_GL_CMD_ADD_BLEND:
                    s_additiveBlending(ptr);
                    break;

                case MGPI_GL_CMD_ALPHA_BLEND:
                    s_alphaBlending(ptr);
                    break;

                case MGPI_GL_CMD_MULT_2X_BLEND:
                    s_mult2xBlending(ptr);
                    break;

                case MGPI_GL_CMD_MULT_BLEND:
                    s_multBlending(ptr);
                    break;

                case MGPI_GL_CMD_SHADERS:
                    s_shaders(ptr);
                    break;

                case MGPI_GL_CMD_USER:
                    ptr->usrcmds.user_gl_cmd_pre[ptr->usrcmds.current](ptr);
                    break;

                case MGPI_GL_CMD_RENDER_2D:
                    s_render2D(ptr);
                    break;

                case MGPI_GL_CMD_MULTITEXTURE:
                    s_multitextured(ptr);
                    break;

                case MGPI_GL_CMD_FOG_LINEAR:
                    s_fogLinear(ptr);
                    break;

                case MGPI_GL_CMD_FOG_EXP:
                    s_fogExponent(ptr);
                    break;

                case MGPI_GL_CMD_FOG_EXP2:
                    s_fogLinear(ptr);
                    break;

                    /* itd. */

                default: break;
            }
        if (ptr->gl_var->n) s_variables(ptr);
        ++i;
    }

    return;
}

/**
 * Disables all combined called GL commands for the given rendering object.
 */
static void s_disableCombinedCommands(rendnode_t *ptr) {
    int i = 0;

    while (i < ptr->number_of_cmds) {
        switch (ptr->combined_cmds_array[i]) {
            case MGPI_GL_CMD_ADD_BLEND:
            case MGPI_GL_CMD_ALPHA_BLEND:
            case MGPI_GL_CMD_MULT_2X_BLEND:
            case MGPI_GL_CMD_MULT_BLEND:
                s_disableBlending(ptr);
                break;

            case MGPI_GL_CMD_SHADERS:
                s_disableShaders(ptr);
                break;

            case MGPI_GL_CMD_TEXTURE:
                s_disableTextured(ptr);
                break;

            case MGPI_GL_CMD_USER:/* TEST IT!!! */
                ptr->usrcmds.user_gl_cmd_post[ptr->usrcmds.current](ptr);
                ++ptr->usrcmds.current;
                if (ptr->usrcmds.current == ptr->usrcmds.num) ptr->usrcmds.current = 0;
                break;

            case MGPI_GL_CMD_RENDER_2D:
                s_disableRender2D(ptr);
                break;

            case MGPI_GL_CMD_MULTITEXTURE:
                s_disableMultitextured(ptr);
                break;

            case MGPI_GL_CMD_FOG_LINEAR:
            case MGPI_GL_CMD_FOG_EXP:
            case MGPI_GL_CMD_FOG_EXP2:
                s_disableFog(ptr);
                break;

                /* itd. */

            default: break;
        }
        ++i;
    }

    return;
}

/**
 * Sets a GLSL variable of the given type, class and name in a selected program.
 */
static int s_setVariable(glsl_t type, glsl_class_t _class, const char *name, ...) {/* TEST IT!!! */
    GLint location = -1;
    va_list ap;

    if (!glIsProgram(s_nSelectedProgramID)) {
        MGPI_PostMessagef("%s: selected program is not valid", __FUN__);
        ENGINE_Exit();
        exit(EXIT_FAILURE);
    }

    if (_class == MGPI_GLSL_ATTRIBUTE &&
            (type == MGPI_GLSL_MATRIX2 ||
            type == MGPI_GLSL_MATRIX2x3 ||
            type == MGPI_GLSL_MATRIX2x4 ||
            type == MGPI_GLSL_MATRIX3 ||
            type == MGPI_GLSL_MATRIX3x2 ||
            type == MGPI_GLSL_MATRIX3x4 ||
            type == MGPI_GLSL_MATRIX4 ||
            type == MGPI_GLSL_MATRIX4x2 ||
            type == MGPI_GLSL_MATRIX4x3
            )) {
        MGPI_PostMessagef("%s: such call is invalid (MGPI_GLSL_ATTRIBUTE + MGPI_GLSL_MATRIXxxx)", __FUN__);
        ENGINE_Exit();
        exit(EXIT_FAILURE);
    }

    if (_class == MGPI_GLSL_UNIFORM)
        location = glGetUniformLocation(s_nSelectedProgramID, name);
    else if (_class == MGPI_GLSL_ATTRIBUTE || _class == MGPI_GLSL_ATTRIBUTE_ARRAY)
        location = glGetAttribLocation(s_nSelectedProgramID, name); /* TODO: check how many has been called... WRONG!!!!*/
    else {
        MGPI_PostMessagef("%s: invalid GLSL type class", __FUN__);
        ENGINE_Exit();
        exit(EXIT_FAILURE);
    }

    va_start(ap, name);

    switch (type) {
        case MGPI_GLSL_FLOAT1:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform1f(location, va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib1f(location, va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 1, GL_FLOAT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_FLOAT2:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform2f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib2f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_FLOAT3:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform3f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib3f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_FLOAT4:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform4f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib4f(location, va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble),
                    va_arg(ap, GLdouble));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_FLOATnv:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform4fv(location, va_arg(ap, GLsizei), va_arg(ap, GLfloat *));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib4fv(location, va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_INT1:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform1i(location, va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib1s(location, va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 1, GL_INT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_INT2:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform2i(location, va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib2s(location, va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 2, GL_INT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_INT3:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform3i(location, va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib3s(location, va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 3, GL_INT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_INT4:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform4i(location, va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib4s(location, va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint),
                    va_arg(ap, GLint));
            else if (_class == MGPI_GLSL_ATTRIBUTE_ARRAY) {
                GLvoid *pointer = va_arg(ap, GLvoid *);
                glVertexAttribPointer(location, 4, GL_INT, GL_FALSE, 0, pointer);
            }
            break;

        case MGPI_GLSL_INTnv:
            if (_class == MGPI_GLSL_UNIFORM)
                glUniform4iv(location, va_arg(ap, GLsizei), va_arg(ap, GLint *));
            else if (_class == MGPI_GLSL_ATTRIBUTE)
                glVertexAttrib4iv(location, va_arg(ap, GLint *));
            break;

        case MGPI_GLSL_MATRIX2:
            glUniformMatrix2fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX3:
            glUniformMatrix3fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX4:
            glUniformMatrix4fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX2x3:
            glUniformMatrix2x3fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX3x2:
            glUniformMatrix3x2fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX2x4:
            glUniformMatrix2x4fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX4x2:
            glUniformMatrix4x2fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX3x4:
            glUniformMatrix3x4fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        case MGPI_GLSL_MATRIX4x3:
            glUniformMatrix4x3fv(location, va_arg(ap, GLsizei), va_arg(ap, GLint), va_arg(ap, GLfloat *));
            break;

        default: break;
    }

    va_end(ap);
    return 0;
}

/**
 * Sets the given commands routine as a GL commands for the object given.
 */
static int s_setCommands(glcmds_t gl_cmd, va_list ap) {
    rendnode_t *obj = s_pAuxPointer;
    int i;

    if (obj) {
        switch (gl_cmd) {
            case MGPI_GL_CMD_TEXTURE:
                obj->gl_commands_pre = s_textured;
                obj->gl_commands_post = s_disableTextured;
                break;

            case MGPI_GL_CMD_ALPHA_BLEND:
                obj->gl_commands_pre = s_alphaBlending;
                obj->gl_commands_post = s_disableBlending;
                break;

            case MGPI_GL_CMD_EMPTY:
                obj->gl_commands_pre = NULL;
                obj->gl_commands_post = NULL;
                break;

            case MGPI_GL_CMD_MULT_2X_BLEND:
                obj->gl_commands_pre = s_mult2xBlending;
                obj->gl_commands_post = s_disableBlending;
                break;

            case MGPI_GL_CMD_ADD_BLEND:
                obj->gl_commands_pre = s_additiveBlending;
                obj->gl_commands_post = s_disableBlending;
                break;

            case MGPI_GL_CMD_MULT_BLEND:
                obj->gl_commands_pre = s_multBlending;
                obj->gl_commands_post = s_disableBlending;
                break;

            case MGPI_GL_CMD_MULTITEXTURE:
                obj->gl_commands_pre = s_multitextured;
                obj->gl_commands_post = s_disableMultitextured;
                break;

            case MGPI_GL_CMD_USER:
#ifdef WIN32
                obj->gl_commands_pre = va_arg(ap, void *);
                obj->gl_commands_post = va_arg(ap, void *);
#else
                obj->gl_commands_pre = va_arg(ap, void (*)(struct _rendnode *));
                obj->gl_commands_post = va_arg(ap, void (*)(struct _rendnode *));
#endif
                break;

            case MGPI_GL_CMD_COMBINED:
                obj->gl_commands_pre = s_combinedCommands;

                obj->number_of_cmds = va_arg(ap, int);
                obj->combined_cmds_array = (int *) calloc(obj->number_of_cmds, sizeof (int));
                MGPI_CollectGarbage(obj->combined_cmds_array);

                for (i = 0; i < obj->number_of_cmds; ++i) {
                    obj->combined_cmds_array[i] = va_arg(ap, int);
                    if (obj->combined_cmds_array[i] == MGPI_GL_CMD_COMBINED) {
                        MGPI_PostMessagef("%s: invalid combined commands usage",__FUN__);
                        return 0;
                    } else if (obj->combined_cmds_array[i] == MGPI_GL_CMD_USER) {
                        if (obj->usrcmds.free < MGPI_MAX_USER_COMMANDS) {
#ifdef WIN32
                            obj->usrcmds.user_gl_cmd_pre[obj->usrcmds.free] = va_arg(ap, void *);
                            obj->usrcmds.user_gl_cmd_post[obj->usrcmds.free] = va_arg(ap, void *);
#else
                            obj->usrcmds.user_gl_cmd_pre[obj->usrcmds.free] = va_arg(ap, void (*)(struct _rendnode *));
                            obj->usrcmds.user_gl_cmd_post[obj->usrcmds.free] = va_arg(ap, void (*)(struct _rendnode *));
#endif
                            ++obj->usrcmds.free;
                            ++obj->usrcmds.num;
                        } else {
                            MGPI_PostMessagef("%s: max number of user defined GL commands exceeded", __FUN__);
                            ENGINE_Exit();
                            exit(EXIT_FAILURE);
                        }
                    }
                }

                obj->gl_commands_post = s_disableCombinedCommands;

                break;

            case MGPI_GL_CMD_SHADERS:
                obj->gl_commands_pre = s_shaders;
                obj->gl_commands_post = s_disableShaders;
                break;

            case MGPI_GL_CMD_RENDER_2D:
                obj->gl_commands_pre = s_render2D;
                obj->gl_commands_post = s_disableRender2D;
                break;

            case MGPI_GL_CMD_FOG_LINEAR:
                obj->gl_commands_pre = s_fogLinear;
                obj->gl_commands_post = s_disableFog;
                break;

            case MGPI_GL_CMD_FOG_EXP:
                obj->gl_commands_pre = s_fogExponent;
                obj->gl_commands_post = s_disableFog;
                break;

            case MGPI_GL_CMD_FOG_EXP2:
                obj->gl_commands_pre = s_fogExponent2;
                obj->gl_commands_post = s_disableFog;
                break;

                /* and so on... */

            default:
                obj->gl_commands_pre = NULL;
                obj->gl_commands_post = NULL;
                break;
        }
    }
    return 0;
}

/**
 * Sets the given commands routine as a GL commands for the object given.
 */
static int s_setObjectGLCommands(int hobj, glcmds_t gl_cmd, ...) {
    rendnode_t *obj = s_getObject(hobj);
    va_list ap;
    int i;

    if (!obj) {
        MGPI_PostMessagef("%s: given handle is of no valid object: %d", __FUN__, hobj);
        return 0;
    }

    switch (gl_cmd) {
        case MGPI_GL_CMD_TEXTURE:
            obj->gl_commands_pre = s_textured;
            obj->gl_commands_post = s_disableTextured;
            break;

        case MGPI_GL_CMD_ALPHA_BLEND:
            obj->gl_commands_pre = s_alphaBlending;
            obj->gl_commands_post = s_disableBlending;
            break;

        case MGPI_GL_CMD_EMPTY:
            obj->gl_commands_pre = NULL;
            obj->gl_commands_post = NULL;
            break;

        case MGPI_GL_CMD_MULT_2X_BLEND:
            obj->gl_commands_pre = s_mult2xBlending;
            obj->gl_commands_post = s_disableBlending;
            break;

        case MGPI_GL_CMD_ADD_BLEND:
            obj->gl_commands_pre = s_additiveBlending;
            obj->gl_commands_post = s_disableBlending;
            break;

        case MGPI_GL_CMD_MULT_BLEND:
            obj->gl_commands_pre = s_multBlending;
            obj->gl_commands_post = s_disableBlending;
            break;

        case MGPI_GL_CMD_MULTITEXTURE:
            obj->gl_commands_pre = s_multitextured;
            obj->gl_commands_post = s_disableMultitextured;
            break;

        case MGPI_GL_CMD_USER:
            va_start(ap, gl_cmd);
#ifdef WIN32
            obj->gl_commands_pre = va_arg(ap, void *);
            obj->gl_commands_post = va_arg(ap, void *);
#else
            obj->gl_commands_pre = va_arg(ap, void (*)(struct _rendnode *));
            obj->gl_commands_post = va_arg(ap, void (*)(struct _rendnode *));
#endif
            va_end(ap);
            break;

        case MGPI_GL_CMD_COMBINED:
            obj->gl_commands_pre = s_combinedCommands;

            va_start(ap, gl_cmd);
            obj->number_of_cmds = va_arg(ap, int);
            obj->combined_cmds_array = (int *) calloc(obj->number_of_cmds, sizeof (int));
            MGPI_CollectGarbage(obj->combined_cmds_array);

            for (i = 0; i < obj->number_of_cmds; ++i) {
                obj->combined_cmds_array[i] = va_arg(ap, int);
                if (obj->combined_cmds_array[i] == MGPI_GL_CMD_COMBINED) {
                    MGPI_PostMessagef("%s: invalid combined commands usage", __FUN__);
                    return 0;
                } else if (obj->combined_cmds_array[i] == MGPI_GL_CMD_USER) {
                    if (obj->usrcmds.free < MGPI_MAX_USER_COMMANDS) {
#ifdef WIN32
                        obj->usrcmds.user_gl_cmd_pre[obj->usrcmds.free] = va_arg(ap, void *);
                        obj->usrcmds.user_gl_cmd_post[obj->usrcmds.free] = va_arg(ap, void *);
#else
                        obj->usrcmds.user_gl_cmd_pre[obj->usrcmds.free] = va_arg(ap, void (*)(struct _rendnode *));
                        obj->usrcmds.user_gl_cmd_post[obj->usrcmds.free] = va_arg(ap, void (*)(struct _rendnode *));
#endif
                        ++obj->usrcmds.free;
                        ++obj->usrcmds.num;
                    } else {
                        MGPI_PostMessagef("%s: max number of user defined GL commands exceeded", __FUN__);
                        ENGINE_Exit();
                        exit(EXIT_FAILURE);
                    }
                }
            }
            va_end(ap);

            obj->gl_commands_post = s_disableCombinedCommands;

            break;

        case MGPI_GL_CMD_SHADERS:
            obj->gl_commands_pre = s_shaders;
            obj->gl_commands_post = s_disableShaders;
            break;

        case MGPI_GL_CMD_RENDER_2D:
            obj->gl_commands_pre = s_render2D;
            obj->gl_commands_post = s_disableRender2D;
            break;

        case MGPI_GL_CMD_FOG_LINEAR:
            obj->gl_commands_pre = s_fogLinear;
            obj->gl_commands_post = s_disableFog;
            break;

        case MGPI_GL_CMD_FOG_EXP:
            obj->gl_commands_pre = s_fogExponent;
            obj->gl_commands_post = s_disableFog;
            break;

        case MGPI_GL_CMD_FOG_EXP2:
            obj->gl_commands_pre = s_fogExponent2;
            obj->gl_commands_post = s_disableFog;
            break;

            /* and so on... */

        default:
            obj->gl_commands_pre = NULL;
            obj->gl_commands_post = NULL;
            break;
    }

    return hobj;
}

/**
 * Verifies framebuffer completeness.
 */
static void s_verifyFramebuffer(void) {
	switch(glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENTT", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		case GL_FRAMEBUFFER_UNSUPPORTED:
			MGPI_PostMessagef("%s: framebuffer error: GL_FRAMEBUFFER_UNSUPPORTED", __FUN__);
			ENGINE_Exit();
            exit(EXIT_FAILURE);

		default: break;
	}
	return;
}

/**
 * Gets engine property of the given name.
 */
static int s_getProperty(pname_t property, ...) {
    va_list ap;
    int *ptr;
    GLuint *uptr;
    char *cptr;

    va_start(ap, property);

    switch (property) {
        case MGPI_SCREEN_BITS: /* gets the rendering context surface bits-pre-pixel number */
            ptr = va_arg(ap, int *);
            (*ptr) = s_cpInfo->vfmt->BitsPerPixel;
            break;

        case MGPI_SHADERS: /* gets a set of shaders currently attached to the selected program */
            uptr = va_arg(ap, GLuint *);
            ptr = va_arg(ap, int *);
            glGetAttachedShaders(s_nSelectedProgramID, va_arg(ap, int), ptr, uptr);
            break;

        case MGPI_SHADER_PROGRAM: /* gets the engine's main program */
            uptr = va_arg(ap, GLuint *);
            (*uptr) = s_nMainProgramID;
            break;

        case MGPI_UNIFORM_LOCATION:
            cptr = va_arg(ap, char *);
            ptr = va_arg(ap, int *);

            if (s_fUseShaders && glIsProgram(s_nSelectedProgramID))
                (*ptr) = glGetUniformLocation(s_nSelectedProgramID, (const char *) cptr);
            else (*ptr) = -1;

            break;

        default: break;
    }

    va_end(ap);
    return (!0);
}

/**
 * Sets a given name engine property.
 */
static int s_setProperty(pname_t property, ...) {
    float *vector;
    float4 v = MGPI_DEFAULT_VECTOR;
    cam_t *tmp;
    int index, cam, size, n, i, w, h, samples;
    rendnode_t *obj;
    GLenum e;
    static MGPI_BOOL flag;
    const char *name;
    float f = 0.0f;
    void (*fpointer)(rendnode_t *) = NULL;
    GLuint *ids, *targs, ms;
    const char **names;
    struct _multitex *mtnames;

    va_list ap;
    va_start(ap, property);

    switch (property) {

        case MGPI_FOG_PARAMETER: /* sets engine fog parameters */
            index = va_arg(ap, int);
            switch (index) {
                case MGPI_COLOR:
                    s_pFogColor[X] = (float) va_arg(ap, double);
                    s_pFogColor[Y] = (float) va_arg(ap, double);
                    s_pFogColor[Z] = (float) va_arg(ap, double);
                    break;

                case MGPI_VECTOR:
                    s_pFogBoundries[X] = (float) va_arg(ap, double);
                    s_pFogBoundries[Y] = (float) va_arg(ap, double);
                    break;

                case MGPI_VALUE:
                    s_nFogDensity = (float) va_arg(ap, double);
                    break;

                default: break;
            }
            break;

        case MGPI_SHADERS: /* turns on/off shaders functionality */
            if (s_fUseShaders) {
                s_fUseShaders = MGPI_FALSE;
                if (glIsProgram(s_nMainProgramID)) glDeleteProgram(s_nMainProgramID);
                else MGPI_PostMessagef("%s: the engine main program is invalid: %d", __FUN__, s_nMainProgramID);
                s_nMainProgramID = 0;
            } else {
                s_fUseShaders = MGPI_TRUE;
                s_nMainProgramID = glCreateProgram();
                s_nSelectedProgramID = s_nMainProgramID;
            }
            break;

        case MGPI_OBJECT_EFFECT: /* sets a set of shaders for given object rendering */
            if (s_fUseShaders && s_pAuxPointer) {
                obj = s_pAuxPointer;
                obj->shaders.num = va_arg(ap, int);
                obj->shaders.shaders = va_arg(ap, GLuint *);
                if (glIsProgram(obj->shaders.program_id)) glDeleteProgram(obj->shaders.program_id);
                obj->shaders.program_id = s_setupProgram(obj->shaders.shaders, obj->shaders.num);
            }
            break;

            /* TODO: variables setup... */
        case MGPI_VERTEX_SHADER_OBJECT:
            if (s_fUsePrecompiledShaders && s_pAuxPointer) {
#ifdef GL_ARB_vertex_program
                char *program;
                glEnable(GL_VERTEX_PROGRAM_ARB);
                {
                    glGenProgramsARB(1, &s_pAuxPointer->shaders.shaders_programs[0]);
                    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, s_pAuxPointer->shaders.shaders_programs[0]);
                    name = va_arg(ap, const char *);
                    program = (char *) fload((char *) name);
                    glProgramStringARB(GL_VERTEX_PROGRAM_ARB,
                            GL_PROGRAM_FORMAT_ASCII_ARB,
                            strlen(program), (const GLbyte *) program);
                    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
                }
                glDisable(GL_VERTEX_PROGRAM_ARB);
#endif
            }
            break;

        case MGPI_FRAGMENT_SHADER_OBJECT:
            if (s_fUsePrecompiledShaders && s_pAuxPointer) {
#ifdef GL_ARB_fragment_program
                char *program;
                glEnable(GL_FRAGMENT_PROGRAM_ARB);
                {
                    glGenProgramsARB(1, &s_pAuxPointer->shaders.shaders_programs[2]);
                    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, s_pAuxPointer->shaders.shaders_programs[2]);
                    name = va_arg(ap, const char *);
                    program = (char *) fload((char *) name);
                    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB,
                            GL_PROGRAM_FORMAT_ASCII_ARB,
                            strlen(program), (const GLbyte *) program);
                    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
                }
                glDisable(GL_FRAGMENT_PROGRAM_ARB);
#endif
            }
            break;

        case MGPI_UNIFORM_VALUE:
            if (s_fUseShaders && s_pAuxPointer) {
                i = s_pAuxPointer->gl_var->n;

                if (i >= MGPI_MAX_UNIFORM_VARIABLES) {
                    MGPI_PostMessagef("%s: maximum number of uniform variables achieved", __FUN__);
                    break;
                }
                s_pAuxPointer->gl_var[i].location = va_arg(ap, GLuint);
                s_pAuxPointer->gl_var[i].type = va_arg(ap, GLuint);
                s_pAuxPointer->gl_var[i].pointer = va_arg(ap, GLfloat *);
                s_pAuxPointer->gl_var[i].length = va_arg(ap, int);
                s_pAuxPointer->gl_var->n++;
            }
            break;

		case MGPI_VERTEX_ATTRIBUTE_ARRAY://???????????????????????
			/* TODO: vertex attribute array resitration */
			/*
			ctx->selectObject(RENDERING_OBJECT_ID);
			ctx->selectProgram(MGPI_OBJECT);
			ctx->setProperty(MGPI_VERTEX_ATTRIBUTE_ARRAY, "Weight", GL_FLOAT, 1, GL_FALSE, 0, pointer);
			*/
			break;

        case MGPI_SHADER_PROGRAM: /* sets a given program for selected object rendering */
            if (s_fUseShaders && s_pAuxPointer)
                s_pAuxPointer->shaders.program_id = va_arg(ap, GLuint);
            break;

        case MGPI_PRECOMPILED_SHADERS:
            s_fUsePrecompiledShaders = !s_fUsePrecompiledShaders;
            break;

		case MGPI_DEPTH:
			if (s_nFramebuffersNumber > 0) {
				s_nDepthFormat = va_arg(ap, GLuint);
			}
			break;

		case MGPI_COLOR:
			if (s_nFramebuffersNumber > 0) {
				s_nColorFormat = va_arg(ap, GLuint);
			}
			break;

		case MGPI_MULTISAMPLE:
			if (s_nFramebuffersNumber > 0) {
				s_nSamplesUsed = va_arg(ap, int);
			}
			break;

		case MGPI_FRAMEBUFFER_MULTISAMPLE://!!!!!!!!!!!!!!!!! REMOVE UNUSED BUFFERS !!!!!!!!!!!!!!!!
			if (s_nFramebuffersNumber > 0) {
				obj = &s_oFramebufferNode;// NOTE: use array!!!!!?????

				if (!obj->fbuffer.multisample) {
					w = va_arg(ap, int);
					h = va_arg(ap, int);

					obj->fbuffer.w = w;
					obj->fbuffer.h = h;

					if (!obj->fbuffer.multisample) {
						glGenFramebuffers(1, &obj->fbuffer.multisample);
						glBindFramebuffer(GL_FRAMEBUFFER, obj->fbuffer.multisample);
						{
							glGetIntegerv(GL_MAX_SAMPLES, &samples);
							samples = (s_nSamplesUsed > samples) ? samples : s_nSamplesUsed;
							ms = (GLuint) g3dGenMultisampleRenderbuffer(s_nColorFormat, w, h, samples);//!!!!!!??????
							glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, ms);

							obj->fbuffer.depth = (GLuint) g3dGenMultisampleRenderbuffer(s_nDepthFormat, w, h, samples);
							glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, obj->fbuffer.depth);

							s_verifyFramebuffer();
						}
						glBindFramebuffer(GL_FRAMEBUFFER, 0);
					}
				}
			}/* setProperty(MGPI_FRAMEBUFFER_MULTISAMPLE, w, h); */
			break;

        case MGPI_FRAMEBUFFER: /* sets up or selects given framebuffer */
            if (s_nFramebuffersNumber > 0) {
                obj = &s_oFramebufferNode;// NOTE: use array!!!!!?????
                obj->fbuffer.target = va_arg(ap, GLuint);

				if ((int)obj->fbuffer.target >= MGPI_DEFAULT_FRAMEBUFFER && 
					(int)obj->fbuffer.target < s_nFramebuffersNumber) {
					obj->fbuffer.used_id = s_aFramebuffers[obj->fbuffer.target];

				} else if ((int)obj->fbuffer.target >= s_nFramebuffersNumber &&
						   (int)obj->fbuffer.target < GL_TEXTURE_2D) {
					MGPI_PostMessagef("%s: warning: given number is higher then the number of created framebuffers", __FUN__);

				} else {
					obj->fbuffer.attachment = va_arg(ap, GLuint);
					obj->fbuffer.index = i = va_arg(ap, int);
					obj->fbuffer.used_id = s_aFramebuffers[i];
					obj->fbuffer.output = va_arg(ap, GLuint);
					w = va_arg(ap, int);
					h = va_arg(ap, int);

					obj->fbuffer.w = w;
					obj->fbuffer.h = h;

					n = 0;
					while (n < s_nMaxAttachments && s_aAttachments[i][n] && s_aAttachments[i][n] != obj->fbuffer.attachment) n++;
					if (n < s_nMaxAttachments) 
						s_aAttachments[i][n] = obj->fbuffer.attachment;
					else MGPI_PostMessagef("%s: warning: max number of attachments achieved", __FUN__); 

					glBindFramebuffer(GL_FRAMEBUFFER, obj->fbuffer.used_id);
					{
						switch(obj->fbuffer.target) {
							case GL_TEXTURE_3D:
								obj->fbuffer.slice = va_arg(ap, GLuint);
								glFramebufferTexture3D(GL_FRAMEBUFFER, obj->fbuffer.attachment, obj->fbuffer.target, obj->fbuffer.output, 0, obj->fbuffer.slice);
								break;

							default:
								glFramebufferTexture2D(GL_FRAMEBUFFER, obj->fbuffer.attachment, obj->fbuffer.target, obj->fbuffer.output, 0);
								break;
						}
						
						if (!obj->fbuffer.depth) {
							obj->fbuffer.depth = (GLuint) g3dGenRenderbuffer(s_nDepthFormat, w, h);
							glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, obj->fbuffer.depth);
						}
						/*if (!obj->fbuffer.stencil) {
							obj->fbuffer.stencil = (GLuint) g3dGenRenderbuffer(GL_STENCIL_INDEX, w, h);
							glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, obj->fbuffer.stencil);
						}*/

						s_verifyFramebuffer();
					}
					glBindFramebuffer(GL_FRAMEBUFFER, 0);
				}
            }/* setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, MGPI_DEFAULT_FRAMEBUFFER, tex, w, h); */
			 /* setProperty(MGPI_FRAMEBUFFER, MGPI_DEFAULT_FRAMEBUFFER+3); */
            break;

        case MGPI_RENDER_TARGET: /* sets a render target - "render-to-texture" */
            index = va_arg(ap, int);
            flag = (MGPI_BOOL) va_arg(ap, int);

            if (s_nFramebuffersNumber > 0) {
                n = 0;
                while (n < s_nMaxAttachments && s_aAttachments[s_oFramebufferNode.fbuffer.index][n]) n++;
                if (n == 1) 
					glDrawBuffer(s_aAttachments[s_oFramebufferNode.fbuffer.index][0]);
                else if (n) 
					glDrawBuffers(n, s_aAttachments[s_oFramebufferNode.fbuffer.index]);
                else MGPI_PostMessagef("%s: warning: no attachments given", __FUN__);

                switch (index) {
                    case MGPI_TEXTURE:
						if (flag) {
							s_framebufferR2T(&s_oFramebufferNode);
							glClear(s_fClearFlags);
						}
                        else s_disableFramebufferR2T(&s_oFramebufferNode);
                        break;

                    default: break;
                }
				s_verifyFramebuffer();

                if (!flag) while (n++ < s_nMaxAttachments) s_aAttachments[s_oFramebufferNode.fbuffer.index][n] = 0;
            }/* setProperty(MGPI_RENDER_TARGET, MGPI_TEXTURE, MGPI_ENABLE); */
            break;

		case MGPI_PREPROCESS:
			if (s_nFramebuffersNumber > 0) {

				// ...

			}/* setProperty(MGPI_PREPROCESS, tex_id, "SubsurfaceScattering.glsl"); */
			break;

		case MGPI_POSTPROCESS:
			if (s_nFramebuffersNumber > 0) {
				// 1. Create 4 textures: FCT, CTA, CTB and DT
				if (!glIsTexture(s_nPPTextures[FCT]))
					s_nPPTextures[FCT] = g3dGenEmptyTexture2D(s_nScreenWidth, s_nScreenHeight, GL_FLOAT,
															  GL_RGBA32F, GL_RGBA, GL_TEXTURE_2D);
				if (!glIsTexture(s_nPPTextures[CTA]))
					s_nPPTextures[CTA] = g3dGenEmptyTexture2D(s_nScreenWidth, s_nScreenHeight, GL_FLOAT,
															  GL_RGBA32F, GL_RGBA, GL_TEXTURE_2D);
				if (!glIsTexture(s_nPPTextures[CTB]))
					s_nPPTextures[CTB] = g3dGenEmptyTexture2D(s_nScreenWidth, s_nScreenHeight, GL_FLOAT,
															  GL_RGBA32F, GL_RGBA, GL_TEXTURE_2D);
				if (!glIsTexture(s_nPPTextures[DT]))
					s_nPPTextures[DT] = g3dGenEmptyTexture2D(s_nScreenWidth, s_nScreenHeight, GL_FLOAT,
															  GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_TEXTURE_2D);
				// 2. Prepare the pixelbuffer for color textures copies
				g3dPreparePixelBuffer(s_nScreenWidth, s_nScreenHeight, 4 * sizeof(float));//??????

				// 3. Attach FCT, as the zero-pass output, to the selected engine framebuffer color attachment
				s_setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, MGPI_DEFAULT_FRAMEBUFFER,
							  s_nPPTextures[FCT], s_nScreenWidth, s_nScreenHeight);

				// 4. Attach DT to the selected engine framebuffer depth attachment
				s_setProperty(MGPI_FRAMEBUFFER, GL_TEXTURE_2D, GL_DEPTH_ATTACHMENT, MGPI_DEFAULT_FRAMEBUFFER, 
							  s_nPPTextures[DT], s_nScreenWidth, s_nScreenHeight);

				// 5. Effect preparation
				n = va_arg(ap, int);
				names = (const char **) va_arg(ap, const char **);

				if (!s_setupEffect(names, n, &s_pPPEffect)) {
					// TODO: Add message
					ENGINE_Exit();
					exit(EXIT_FAILURE);
				}
				s_fDoMultipassProcess = MGPI_TRUE;
				
				// 6. Textures preparation
				GLuint mtt[4] = { GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D, GL_TEXTURE_2D };
				s_setProperty(MGPI_MULTITEXTURE, 4, names, mtt, s_nPPTextures);

			}/* const char *passes[4] = { "HDRI.Vertex", "HDRI.BrightPass", "HDRI.PostGlow", "HDRI.ToneMappping" };
				setProperty(MGPI_POSTPROCESS, 4, passes); */
			break;

        case MGPI_GL_ON: /* turns on GL capability */
            glEnable(va_arg(ap, GLenum));
            break;

        case MGPI_GL_OFF: /* turns off GL capability */
            glDisable(va_arg(ap, GLenum));
            break;

        case MGPI_GL_COMMANDS: /* the same as setObjectGLCommands() */
            if (s_pAuxPointer) {
                index = va_arg(ap, int);
                s_setCommands(index, ap);
            }
            break; /* setProperty(MGPI_GL_COMMANDS, cmd, ...); */

        case MGPI_TRANSFORMATIONS: /* the same as the setObjectTransformations() */
            if (s_pAuxPointer) {
#ifndef WIN32
                fpointer = va_arg(ap, void (*)(struct _rendnode *));
#else
                fpointer = va_arg(ap, void *);
#endif
                s_setTransformations(fpointer);
            }
            break;

        case MGPI_MODELVIEW_MATRIX:
            vector = va_arg(ap, float *);
            if (vector) g_fModelView = (GLfloat *) vector;
            break;

        case MGPI_PROJECTION_MATRIX:
            vector = va_arg(ap, float *);
            if (vector) g_fProjection = (GLfloat *) vector;
            break;

        case MGPI_LIGHTS: /* turns on and off the GL lights */
            if (s_fUseLight) {
                s_fUseLight = MGPI_FALSE;
                glDisable(GL_LIGHTING);
            } else {
                s_fUseLight = MGPI_TRUE;
                glEnable(GL_LIGHTING);
            }
            break;

        case MGPI_CAMERA_VIEWPORT: /* sets camera view port */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->w = va_arg(ap, int);
            tmp->h = va_arg(ap, int);
            s_nScreenWidth = tmp->w;
            s_nScreenHeight = tmp->h;
            break;

        case MGPI_CAMERA_ROTATION_MODE:
            n = va_arg(ap, int);
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->mode = n;
            break;

        case MGPI_CAMERA_MAX_YAW_PITCH_ROLL:
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->max_ypr[X] = vector[X];
            tmp->max_ypr[Y] = vector[Y];
            tmp->max_ypr[Z] = vector[Z];
            break;

        case MGPI_CAMERA_MIN_YAW_PITCH_ROLL:
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->min_ypr[X] = vector[X];
            tmp->min_ypr[Y] = vector[Y];
            tmp->min_ypr[Z] = vector[Z];
            break;

        case MGPI_CAMERA_POSITION: /* sets camera position */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->position[X] = vector[X];
            tmp->position[Y] = vector[Y];
            tmp->position[Z] = vector[Z];
            tmp->position[W] = vector[W];
            break;

        case MGPI_CAMERA_FOVY: /* sets camera field-of-view Y angle */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->fovy = va_arg(ap, double);
            break;

        case MGPI_CAMERA_NEAR: /* sets camera near clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->near_clip_dist = va_arg(ap, double);
            break;

        case MGPI_CAMERA_FAR: /* sets camera far clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->far_clip_dist = va_arg(ap, double);
            break;

        case MGPI_CAMERA_FORWARD: /* sets camera forward vector */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->f[X] = vector[X];
            tmp->f[Y] = vector[Y];
            tmp->f[Z] = vector[Z];
            tmp->f[W] = vector[W];
            break;

        case MGPI_CAMERA_UP: /* sets camera up vector */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->u[X] = vector[X];
            tmp->u[Y] = vector[Y];
            tmp->u[Z] = vector[Z];
            tmp->u[W] = vector[W];
            break;

        case MGPI_CAMERA_RIGHT: /* sets camera right vector */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            vector = va_arg(ap, float *);
            tmp->r[X] = vector[X];
            tmp->r[Y] = vector[Y];
            tmp->r[Z] = vector[Z];
            tmp->r[W] = vector[W];
            break;

        case MGPI_CAMERA_BOTTOM: /* sets camera bottom clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->bottom_clip_dist = va_arg(ap, double);
            break;

        case MGPI_CAMERA_TOP: /* sets camera top clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->top_clip_dist = va_arg(ap, double);
            break;

        case MGPI_CAMERA_ORIGHT: /* sets camera right clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->right_clip_dist = va_arg(ap, double);
            break;

        case MGPI_CAMERA_OLEFT: /* sets camera left clipping plane dist. */
            cam = va_arg(ap, int);
            index = cam - g_nHandleBase;
            tmp = (g_aSceneCameras + index);
            tmp->left_clip_dist = va_arg(ap, double);
            break;

        case MGPI_LIGHT_POSITION: /* sets light position */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightfv(e, GL_POSITION, v);
            }
            break;

        case MGPI_LIGHT_DIRECTION: /* sets light direction */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightfv(e, GL_SPOT_DIRECTION, v);
            }
            break;

        case MGPI_LIGHT_AMBIENT: /* sets light ambient color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightfv(e, GL_AMBIENT, v);
            }
            break;

        case MGPI_LIGHT_SPECULAR: /* sets light specular color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightfv(e, GL_SPECULAR, v);
            }
            break;

        case MGPI_LIGHT_DIFFUSE: /* sets light diffuse color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightfv(e, GL_DIFFUSE, v);
            }
            break;

        case MGPI_LIGHT_CUTOFF: /* sets light cut-off angle */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                f = (float) va_arg(ap, GLdouble);
                glLightf(e, GL_SPOT_CUTOFF, f);
            }
            break;

        case MGPI_LIGHT_EXPONENT: /* sets light exponent value */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                f = (float) va_arg(ap, GLdouble);
                glLightf(e, GL_SPOT_EXPONENT, f);
            }
            break;

        case MGPI_LIGHT_ATTENUATION: /* sets light attenuation value */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glLightf(e, GL_CONSTANT_ATTENUATION, v[0]);
                glLightf(e, GL_LINEAR_ATTENUATION, v[1]);
                glLightf(e, GL_QUADRATIC_ATTENUATION, v[2]);
            }
            break;

        case MGPI_MATERIAL_AMBIENT: /* sets material ambient color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glMaterialfv(e, GL_AMBIENT, v);
            }
            break;

        case MGPI_MATERIAL_DIFFUSE: /* sets material diffuse color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glMaterialfv(e, GL_DIFFUSE, v);
            }
            break;

        case MGPI_MATERIAL_SPECULAR: /* sets material specular color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glMaterialfv(e, GL_SPECULAR, v);
            }
            break;

        case MGPI_MATERIAL_AMBIENT_DIFFUSE: /* sets one material color for both ambient and diffuse */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glMaterialfv(e, GL_AMBIENT_AND_DIFFUSE, v);
            }
            break;

        case MGPI_MATERIAL_EMISSION: /* sets material emission color */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                v[0] = va_arg(ap, GLdouble);
                v[1] = va_arg(ap, GLdouble);
                v[2] = va_arg(ap, GLdouble);
                glMaterialfv(e, GL_EMISSION, v);
            }
            break;

        case MGPI_MATERIAL_SHININESS: /* sets material shininess */
            if (s_fUseLight) {
                e = va_arg(ap, GLenum);
                f = (float) va_arg(ap, GLdouble);
                glMaterialf(e, GL_SHININESS, f);
            }
            break;

        case MGPI_ANIM_START: /* sets animation start point */
            s_nAnimStart = va_arg(ap, int);
            break;

        case MGPI_ANIM_STOP: /* sets animation stop point */
            s_nAnimStop = va_arg(ap, int);
            break;

        case MGPI_CLEAR_FLAGS: /* sets buffers clear flags */
            s_fClearFlags = va_arg(ap, GLbitfield);
            break;

        case MGPI_USER_DEFINED:
#ifndef WIN32
            s_pRenderUserDefined = va_arg(ap, int(*)(rendnode_t *));
#else
            s_pRenderUserDefined = va_arg(ap, void *);
#endif
            break;

        case MGPI_TOGGLE_FULL_SCREEN: /* toggles fullscreen on/off */
            s_fIsFullScreen = !s_fIsFullScreen;
            if (s_pDisplayContext) SDL_WM_ToggleFullScreen(s_pDisplayContext);
            break;

        case MGPI_LIST_MODE: /* sets object aggregation mode to list */
            s_nListFlag = 1;
            break;

        case MGPI_ARRAY_MODE: /* sets object aggregation mode to array */
            s_nListFlag = 0;
            break;

        case MGPI_TEXTURE: /* sets selected object texture of the given ID and target */
            if (s_pAuxPointer) {
                s_pAuxPointer->tex_id = va_arg(ap, GLuint);
                s_pAuxPointer->tex_targ = va_arg(ap, GLuint);
            }
            break;

        case MGPI_MULTITEXTURE: /* sets selected object multiple textures of multiple texture targets */
            if (s_pAuxPointer) {
                size = va_arg(ap, int);
                names = (const char **) va_arg(ap, const char **);
                targs = (GLuint *) va_arg(ap, GLuint *);
                ids = (GLuint *) va_arg(ap, GLuint *);

                glGetIntegerv(GL_MAX_TEXTURE_UNITS, &n);

                if (size > n) {
                    MGPI_PostMessagef("%s: warning: given number of textures exceedes the max number for the graphic card", __FUN__);
                    size = n;
                }

                mtnames = (struct _multitex *) calloc(size, sizeof (struct _multitex));
                MGPI_CollectGarbage(mtnames);

				if (names && ids && targs) {
					for (i = 0; i < size; ++i) {
						mtnames[i].num = size;
						mtnames[i].param_name = (char *) names[i];
						mtnames[i].tex_name = ids[i];
						mtnames[i].tex_targ = targs[i];
						if (s_fUseShaders && glIsProgram(s_pAuxPointer->shaders.program_id))
							mtnames[i].location = glGetUniformLocation(s_pAuxPointer->shaders.program_id, names[i]);
					}
				}
                s_pAuxPointer->flag = size;
                s_pAuxPointer->mt_ptr = mtnames;
            }
            break; /* setProperty(MGPI_MULTITEXTURE, 2, names, targs, ids); */

        default: break;
    }

    va_end(ap);
    return (!0);
}

/**
 * Converts coordinates to screen space (for text rendering).
 */
static void s_toScreenSpace(void) {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, s_nScreenWidth, 0, s_nScreenHeight);
    glScalef(1, -1, 1);
    glTranslatef(0, -s_nScreenHeight, 0);
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    return;
}

/**
 * Converts coordinates back to model-view space.
 */
static void s_fromScreenSpace(void) {
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    return;
}

/**
 * Creates a camera.
 */
static int s_createCamera(pname_t type) {
    float4 position = MGPI_DEFAULT_VECTOR;
    int cam = -1;
    cam_t *tmp;

    switch (type) {
        case MGPI_PERSPECTIVE:

            cam = g3dAllocVectCam(position);
            tmp = (g_aSceneCameras + (cam - g_nHandleBase));
            tmp->is_perspective = MGPI_TRUE;

            break;
        case MGPI_ORTHOGRAPHIC:

            cam = g3dAllocVectCam(position);
            tmp = (g_aSceneCameras + (cam - g_nHandleBase));

            tmp->near_clip_dist = 0.0f;
            tmp->far_clip_dist = 1.0f;
            tmp->right_clip_dist = 1.0f;
            tmp->left_clip_dist = -1.0;
            tmp->top_clip_dist = 1.0f;
            tmp->bottom_clip_dist = -1.0;

            tmp->is_perspective = MGPI_FALSE;

            break;
        default: break;
    }
    return cam;
}

/**
 * Renders engine diagnostic info.
 */
static void s_renderDiagnostic(void) {
    static float current_time = 0;
    static float last_time = 0;
    char *diagnostic = (char *) calloc(64, sizeof (char));
    float3 pos = {5.0f, 30.0f, 0.0f};
    float3 col = {0.0f, 1.0f, 0.0f};
    float fps_ratio = 0.0f, render_time = 0.0f;
    MGPI_BOOL was_blend = MGPI_FALSE;
    MGPI_BOOL was_texture = MGPI_FALSE;
    MGPI_BOOL was_lighting = MGPI_FALSE;
    MGPI_BOOL was_fog = MGPI_FALSE;

    if (glIsEnabled(GL_BLEND)) {
        glDisable(GL_BLEND);
        was_blend = MGPI_TRUE;
    }

    if (glIsEnabled(GL_TEXTURE_2D)) {
        glDisable(GL_TEXTURE_2D);
        was_texture = MGPI_TRUE;
    }

    if (glIsEnabled(GL_LIGHTING)) {
        glDisable(GL_LIGHTING);
        was_lighting = MGPI_TRUE;
    }

    if (glIsEnabled(GL_FOG)) {
        glDisable(GL_FOG);
        was_fog = MGPI_TRUE;
    }

    last_time = current_time;
    current_time = (float) SDL_GetTicks() / 1000.0;

    render_time = (current_time - last_time);
    fps_ratio = (1 / (current_time - last_time));

    if (fps_ratio < 30.0f && fps_ratio >= 15.0f) col[0]++;
    if (fps_ratio < 15.0f) {
        col[1]--;
        col[0]++;
    }

    sprintf(diagnostic, "FPS: %2.3f, Render time: %2.3f sec.", fps_ratio, render_time);

    glUseProgram(0);

    s_toScreenSpace();
    s_renderString(pos, col, GLUT_BITMAP_8_BY_13, diagnostic);
    s_fromScreenSpace();

    free(diagnostic);

    if (was_blend) glEnable(GL_BLEND);
    if (was_texture) glEnable(GL_TEXTURE_2D);
    if (was_lighting) glEnable(GL_LIGHTING);
    if (was_fog) glEnable(GL_FOG);

    return;
}

/**
 * Updates thread data with one from the front dispatch buffer.
 */
static void s_update(struct _void_ptr *data) {
	static int index = 0;
	s_uiLock = 1;
	memcpy(data, &s_aDispatchBuffer[index], sizeof(struct _void_ptr));
	s_uiLock = 0;
	index = 1 - index;
	return;
}

/**
 * Commits data to back dispatch buffer.
 */
static void s_commit(struct _void_ptr *data) {
	static int index = 1;
	memcpy(&s_aDispatchBuffer[index], data, sizeof(struct _void_ptr));
	while (s_uiLock);
	index = 1 - index;
	return;
}

/**
 * Swaps the buffers.
 */
static void s_commitChanges(void) {
    /*glFlush();*/
    glFinish();
    SDL_GL_SwapBuffers();
    return;
}

/**
 * Updates the given camera state.
 */
static int s_updateCamera(int camera) {
    int index = camera - g_nHandleBase;
    cam_t *tmp = (g_aSceneCameras + index);
    s_nScreenWidth = tmp->w;
    s_nScreenHeight = tmp->h;

    glClear(s_fClearFlags);
    return g3dUpdateCam(camera);
}

/**
 * Sets up the engine.
 */
static int s_setup(engs_t engine_setup, ...) {
    va_list ap;
    void (*user_setup)(void);
    s_nEngineSetup = engine_setup;

    switch (engine_setup) { /* place here the clear flags setup */
        case MGPI_SETUP_USER:
            va_start(ap, engine_setup);
#ifdef WIN32
            user_setup = va_arg(ap, void *);
#else
            user_setup = va_arg(ap, void (*)(void));
#endif
            va_end(ap);

            s_pUserSetup = user_setup;
            break;

        default: break;
    }
    return (!0);
}

/**
 * Prints to console SDL version.
 */
static void s_printSDLVersion(const char* preamble, SDL_version* v) {
    printf("%s %u.%u.%u\n", preamble, v->major, v->minor, v->patch);
    return;
}

/**
 * Prints to console SDL versions.
 */
static void s_printSDLVersions(void) {
    SDL_version ver;

    /* Prints the compile time version */
    SDL_VERSION(&ver);
    s_printSDLVersion("SDL compile-time version", &ver);

    /* Prints the run-time version */
    ver = *SDL_Linked_Version();
    s_printSDLVersion("SDL runtime version", &ver);
    return;
}

/**
 * Causes the current thread to wait for a number of tick.
 * This is used for rendering timming normalization.
 */
static void s_waitFrame(int fps) {
    static int next_tick = 0;
    int this_tick;

    this_tick = (int) SDL_GetTicks();

    if (this_tick < next_tick) SDL_Delay(next_tick - this_tick);
    next_tick = (int) SDL_GetTicks() + (1000 / fps);

    return;
}

/**
 * Returns a pointer to a rendering model from the rendering list.
 */
void *engGetRenderingObject(int id) {
    return s_getObject(id)->rd_ptr;
}

/**
 * Returns a pointer to a rendering node form the rendering list.
 */
rendnode_t *engGetRenderingNode(int id) {/* for extentions... */
    return s_getObject(id);
}

/**
 * Implements the Roeskva interface.
 */
int engImplement(Roeskva_t *engine) {
    if (!s_fIsInUse) {
        if (engine) {
            engine->setup = s_setup;
            engine->loadObject = s_loadObject;
            engine->renderObject = s_renderObject;
            engine->loadObjectComposition = s_loadComposition;
            engine->renderObjectComposition = s_renderComposition;
            engine->loadAnimation = s_loadAnimation;
            engine->releaseObject = s_releaseObject;
            engine->releaseObjectComposition = s_releaseComposition;
            engine->createCamera = s_createCamera;
            engine->updateCamera = s_updateCamera;
            engine->commitChanges = s_commitChanges;
            engine->genList = s_genGLList;
            engine->renderList = s_renderGLList;
            engine->delList = s_deleteGLList;
            engine->setupShader = s_setupShader;
            engine->setupProgram = s_setupProgram;
            engine->selectProgram = s_selectProgram;
            engine->selectObject = s_selectObject;
            engine->getProperty = s_getProperty;
            engine->setProperty = s_setProperty;
            engine->setVariable = s_setVariable;
            engine->setObjectGLCommands = s_setObjectGLCommands;
            engine->setObjectTransformations = s_setObjectTransformations;
            engine->renderObjectAsync = s_callRenderObjectAsync;
            engine->renderObjectCompositionAsync = s_callRenderCompositionAsync;
            engine->renderString = s_renderString;
            engine->renderDiagnostic = s_renderDiagnostic;
            engine->waitFrame = s_waitFrame;
			engine->commit = s_commit;
			engine->update = s_update;

            /* ... */
        }
    }
    return (!0);
}

/**
 * Starts the Roeskva engine.
 */
int engStart(Roeskva_t *engine) {
    if (!s_fIsInUse) {
        s_fIsInUse = MGPI_TRUE;
        s_printSDLVersions();

        if (SDL_Init(SDL_INIT_VIDEO) < 0) {
            MGPI_PostMessagef("%s: error initializing SDL. Error: %s", __FUN__, SDL_GetError());
            return 0;
        }

        s_cpInfo = SDL_GetVideoInfo();

        if (!s_cpInfo) {
            MGPI_PostMessagef("%s: video query failed. Error: %s", __FUN__, SDL_GetError());
            return 0;
        }

        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
        //SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        //SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

#if GL_ARB_multisample == 1
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
#endif

        if (!(s_pDisplayContext = SDL_SetVideoMode(s_nScreenWidth, s_nScreenHeight, s_cpInfo->vfmt->BitsPerPixel,
                SDL_OPENGL | ((s_fIsFullScreen) ? SDL_FULLSCREEN : 0) | SDL_HWSURFACE | SDL_NOFRAME))) {
            MGPI_PostMessagef("%s: video mode set failed: ERROR: %s", __FUN__, SDL_GetError());
            return 0;
        }

        SDL_EnableKeyRepeat(/*SDL_DEFAULT_REPEAT_DELAY*/100, SDL_DEFAULT_REPEAT_INTERVAL);

        glewInit();
        if (!glewIsSupported("GL_VERSION_2_0"))
            MGPI_PostMessagef("%s: warning: no OpenGL 2.0 found", __FUN__);

        switch (s_nEngineSetup) {
            case MGPI_SETUP_DEFAULT:
                glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

                glShadeModel(GL_SMOOTH);

                glClearDepth(1.0f);
                glDepthFunc(GL_LEQUAL);

                glEnable(GL_MULTISAMPLE);
                glEnable(GL_DEPTH_TEST);
                glEnable(GL_CULL_FACE);

                glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
                break;

            case MGPI_SETUP_BUFFERS:
                glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

                glShadeModel(GL_SMOOTH);

                glClearDepth(1.0f);
                glDepthFunc(GL_LEQUAL);

                glEnable(GL_MULTISAMPLE);
                glEnable(GL_DEPTH_TEST);
                glEnable(GL_CULL_FACE);

                glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

                s_genFramebuffers(MGPI_MAX_FRAMEBUFFERS);

                break;

            case MGPI_SETUP_USER:
                if (s_pUserSetup) s_pUserSetup();
                break;

                /* etc... */

            default: break;
        }

    }
    return (!0);
}

/**
 * Stops the Roeskva engine.
 */
int engStop(Roeskva_t *engine) {
    rendnode_t *temp = s_pRenderingList;
    int i = 0;

    s_fIsInUse = MGPI_FALSE;

    if (s_nListFlag) {
        while (temp) {
            switch (temp->type) {

                case MGPI_MODEL_MD5:
                    gsubFreeMD5Model(((md5_model_t *) temp->rd_ptr));
					gsubFreeMD5Anim(((md5_anim_t *) temp->an_ptr));
                    break;

                case MGPI_MODEL_OBJ:
                    gsubFreeOBJModel(((obj_model_t *) temp->rd_ptr));
                    break;

                case MGPI_SKYBOX:
                case MGPI_SKYBOX_CUBEMAP:
                    /*glDeleteTextures(6, (GLuint *) temp->pptr2);
                    gsubFreeSBXModel((char **) temp->rptr);*/
                    break;

                case MGPI_HEIGHTMAP:
                    if (glIsTexture(temp->tex_id)) glDeleteTextures(1, (GLuint *) & temp->tex_id);
                    else MGPI_PostMessagef("%s: given value is no valid texture %d", __FUN__, temp->tex_id);
                    gsubFreeHMPModel((hmp_t *) temp->rd_ptr);
                    break;

                case MGPI_BSPMAP:
                    /* ... */
                    break;

                case MGPI_BILBOARD:
                    /* ... */
                    break;

                    /*case MGPI_MODEL_MD2:
                        glDeleteTextures(((md2_model_t *) temp->rptr)->header.num_skins,
                                (GLuint *) ((md2_model_t *) temp->rptr)->tex_ids);
                        gsubFreeMD2Model((md2_model_t *) temp->rptr);
                        break;*/

                default:
                    if (glIsTexture(temp->tex_id)) glDeleteTextures(1, (GLuint *) & temp->tex_id);
                    else if (temp->tex_id) MGPI_PostMessagef("%s: given value is no valid texture %d", __FUN__, temp->tex_id);
                    break;
            }

            while (i < temp->flag) {
                if (glIsTexture(temp->mt_ptr[i].tex_name))
                    glDeleteTextures(1, (GLuint *) &temp->mt_ptr[i].tex_name);
                ++i;
            }
            i = 0;

            while (i < temp->shaders.num) {
                if (glIsShader(temp->shaders.shaders[i])) glDeleteShader(temp->shaders.shaders[i]);
                ++i;
            }

            if (glIsProgram(temp->shaders.program_id)) glDeleteProgram(temp->shaders.program_id);

#if defined GL_ARB_vertex_program && GL_ARB_fragment_program
            if (s_fUsePrecompiledShaders) {
                if (glIsProgramARB(temp->shaders.shaders_programs[0]))
                    glDeleteProgramsARB(1, &temp->shaders.shaders_programs[0]);

                if (glIsProgramARB(temp->shaders.shaders_programs[2]))
                    glDeleteProgramsARB(1, &temp->shaders.shaders_programs[2]);
            }
#endif
            temp = temp->next;
        }
    } else {
        int k = 0;
        while (k < s_nFree) {
            switch (s_pRenderingArray[k].type) {

                case MGPI_MODEL_MD5:
					gsubFreeMD5Model(((md5_model_t *) s_pRenderingArray[k].rd_ptr));
					gsubFreeMD5Anim(((md5_anim_t *) s_pRenderingArray[k].an_ptr));
                    break;

                case MGPI_MODEL_OBJ:
                    gsubFreeOBJModel(((obj_model_t *) s_pRenderingArray[k].rd_ptr));
                    break;

                case MGPI_SKYBOX:
                case MGPI_SKYBOX_CUBEMAP:
                    if (glIsTexture(s_pRenderingArray[k].tex_id))
                        glDeleteTextures(1, (GLuint *) & s_pRenderingArray[k].tex_id);
                    else MGPI_PostMessagef("%s: given value is no valid texture %d", __FUN__, s_pRenderingArray[k].tex_id);
                    break;

                case MGPI_HEIGHTMAP:
                    if (glIsTexture(s_pRenderingArray[k].tex_id))
                        glDeleteTextures(1, (GLuint *) & s_pRenderingArray[k].tex_id);
                    else MGPI_PostMessagef("%s: given value is no valid texture %d", __FUN__, s_pRenderingArray[k].tex_id);
                    gsubFreeHMPModel((hmp_t *) s_pRenderingArray[k].rd_ptr);
                    break;

                case MGPI_BSPMAP:
                    /* ... */
                    break;

                case MGPI_BILBOARD:
                    /* ... */
                    break;

                    /*case MGPI_MODEL_MD2:
                        glDeleteTextures(((md2_model_t *) s_pRenderingArray[k].rptr)->header.num_skins,
                                (GLuint *) ((md2_model_t *) s_pRenderingArray[k].rptr)->tex_ids);
                        gsubFreeMD2Model((md2_model_t *) s_pRenderingArray[k].rptr);
                        break;*/

                default:
                    if (glIsTexture(s_pRenderingArray[k].tex_id)) glDeleteTextures(1, (GLuint *) & s_pRenderingArray[k].tex_id);
                    else if (s_pRenderingArray[k].tex_id) MGPI_PostMessagef("%s: given value is no valid texture %d", __FUN__, s_pRenderingArray[k].tex_id);
                    break;
            }

            i = 0;
            while (i < s_pRenderingArray[k].flag) {
                if (glIsTexture(s_pRenderingArray[k].mt_ptr[i].tex_name))
                    glDeleteTextures(1, (GLuint *) &s_pRenderingArray[k].mt_ptr[i].tex_name);
                ++i;
            }
            i = 0;

            while (i < s_pRenderingArray[k].shaders.num) {
                if (glIsShader(s_pRenderingArray[k].shaders.shaders[i]))
                    glDeleteShader(s_pRenderingArray[k].shaders.shaders[i]);
                ++i;
            }

            if (glIsProgram(s_pRenderingArray[k].shaders.program_id)) glDeleteProgram(s_pRenderingArray[k].shaders.program_id);

#if defined GL_ARB_vertex_program && GL_ARB_fragment_program
            if (s_fUsePrecompiledShaders) {
                if (glIsProgramARB(s_pRenderingArray[k].shaders.shaders_programs[0]))
                    glDeleteProgramsARB(1, &s_pRenderingArray[k].shaders.shaders_programs[0]);

                if (glIsProgramARB(s_pRenderingArray[k].shaders.shaders_programs[2]))
                    glDeleteProgramsARB(1, &s_pRenderingArray[k].shaders.shaders_programs[2]);
            }
#endif

            ++k;
        }
    }

    if (s_nMainProgramID && glIsProgram(s_nMainProgramID)) glDeleteProgram(s_nMainProgramID);
    s_nMainProgramID = 0;

    if (s_nFramebuffersNumber > 0) s_deleteFramebuffers();
    if (s_pDisplayContext) SDL_FreeSurface(s_pDisplayContext);

    return (!0);
}

/******************************************************************************/

/**
 * Posts a GL error.
 */
void debug_PostGLError(const char *msg) {
    GLenum err = glGetError();
    if (err != GL_NO_ERROR) MGPI_PostMessagef("GL: %s: %s", msg, gluErrorString(err));
    return;
}

/**
 * Posts a SDL error.
 */
void debug_PostSDLError(const char *msg) {
    char *err = NULL;
    err = SDL_GetError();

    if (strlen((const char *) err) > 0) MGPI_PostMessagef("SDL: %s: %s", msg, err);
    return;
}
